# Write the libakgl manual

## Context

libakgl 0.7.0 exports **156 `akgl_*` functions** across 20 public headers, and the only
user-facing documentation is a 404-line `README.md` written as a FAQ. Half of it is
developer process (git hooks, mutation testing, perf suites, memcheck) rather than
anything a person building a game needs, and **the half that is user-facing does not
compile**:

- `PASS(e, akgl_heap_next_spritesheet(&sheet);` — unbalanced parentheses, twice.
- `sprite->frameids = [0, 1, 2, 3];` — not C in any dialect.
- `myactor->state = 9AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_FACE_LEFT);` — a stray `9`.
- `strncpy((char *)&game.name, "sdl3-gametest", 256);` — the exact call `AGENTS.md`
  forbids under **Copying Into Fixed-Width Fields**, in the first snippet a reader sees.
- `int screenwidth = NULL;`

The prose has drifted the same way, and two claims were confirmed false against `src/`:

- `physics.h:195` says `akgl_game_init` passes the `physics.engine` property to
  `akgl_physics_factory`. `akgl_game_init` never calls the factory, and the string
  `physics.engine` appears nowhere in `src/`.
- `registry.h:54` says `akgl_registry_init` creates the registries. `akgl_game_init`
  calls the eight individual initializers and never calls `akgl_registry_init` at all.

`util/assets/littleguy.json` — the sample data for the one shipped demo — still uses the
pre-prefix state names (`"ACTOR_STATE_ALIVE"`) and `velocity_x`, neither of which the
current loader accepts.

This is not a reader routing around typos. It is what happens to samples and prose that
nothing executes, and `AGENTS.md` already makes the argument in another context — *"A
test that has not failed has not been tested"*, *"Do not trust a comment, a TODO entry, or
a CI exclusion that states a premise."* The sibling `akbasic` repository solved exactly
this: every example in its manual is compiled or run by CTest, and a chapter that drifts
from the code turns a job red.

The outcome wanted: a `docs/` manual — introduction, design philosophy, a chapter per
subsystem, and two tutorials building a complete 2D sidescroller and a complete top-down
JRPG — with akbasic's harness ported so no sample in it can rot.

## Decisions taken

| Question | Answer |
|---|---|
| Tutorial assets | Vendor a curated Kenney.nl **CC0** subset, with `LICENSE`, a provenance manifest, and a refresh script |
| Tutorial code | Real compiling, runnable targets under `examples/`, built by default and in CI |
| Existing `README.md` | Split: the FAQ half seeds `docs/` (corrected); the developer-process half stays |
| Known defects | Documented inline in the owning chapter, cross-referenced to `TODO.md` |

---

## The governing editorial rule: reference upstream, do not restate it

**This manual documents libakgl. It does not re-document its dependencies.**

libakgl sits on libakerror, libakstdlib, SDL3, SDL3_image, SDL3_mixer, SDL3_ttf, jansson
and the Tiled map format. Every one of those is documented by its own project, by people
who own the code. A chapter that restates the `ATTEMPT`/`CLEANUP`/`PROCESS`/`HANDLE`/
`FINISH` protocol is a chapter that will be wrong the day libakerror changes it, and
nothing in this repository's test suite would notice — the drift this whole project exists
to fix, reintroduced from a different direction.

So each chapter answers exactly two questions and links out for the rest:

1. **What does libakgl add or constrain here?**
2. **What does a libakgl caller actually write?** — a small, harness-verified example.

| Topic | Owned upstream — link, do not restate | What this manual owes the reader |
|---|---|---|
| `ATTEMPT`/`CLEANUP`/`PROCESS`/`HANDLE`/`FINISH`, `PASS`, `CATCH`, `IGNORE` | `deps/libakerror` | **The status-code tables below**; `akgl_error_init()` ordering; libakgl's own hazards |
| `aksl_strncpy`, `aksl_fclose`, `aksl_atoi` and friends | `deps/libakstdlib` | Which ones libakgl requires you to use, and why (`AGENTS.md` already argues it) |
| `SDL_Renderer`, `SDL_Texture`, events, `SDL_PropertiesID` | SDL3 wiki | The backend vtable, the frame contract, what libakgl does to the renderer's state |
| Audio decoding, `MIX_Audio`, mixers and tracks | SDL3_mixer | `akgl_load_start_bgm`, the track table, the separate three-voice synthesizer (ours) |
| TTF rasterizing and metrics | SDL3_ttf | The font registry, the teardown ordering trap, the rasterize-per-call cost |
| The TMJ map format, layers, tilesets, custom properties | Tiled documentation | libakgl's **extensions** and **limits** — actor objects, `physics.model`, the perspective band, `AKGL_TILEMAP_MAX_*` |
| `json_t`, `json_decref`, the jansson API | jansson manual | `akgl_get_json_*` — which status means "absent" vs "wrong type", and the borrowed-reference rule |

Chapter 04 is the sharpest case. It does **not** teach the error protocol; it says "the
protocol is libakerror's and is documented there", then spends its length on the three
things that are genuinely libakgl's: the status-code tables, the libakgl-specific traps,
and one worked example of a real libakgl call sequence.

### One more decision, forced by what the exploration found

**`docs/` is a narrative manual, not a second reference.** Every header already carries a
substantial `@file`/`@brief` block explaining the subsystem's *design rationale*, and
`Doxyfile` sets `WARN_IF_UNDOCUMENTED = YES` with `WARN_AS_ERROR = FAIL_ON_WARNINGS`, so
an undocumented symbol already fails CI. The gap is navigation and worked examples, not
reference text.

So the chapters teach a task and link to the generated Doxygen for per-function detail.
They do not restate 156 signatures — a hand-copied signature table is exactly the artifact
that drifts, and it would compete with a reference CI already keeps honest. Where a chapter
genuinely needs a declaration or a constant table in front of the reader, it uses a
```` ```c excerpt=include/akgl/heap.h ```` block, so the text *is* the header.

---

## The error-code tables

These are the exception to the rule above, and the reason the exception exists is
concrete: libakerror documents the *mechanism*, but only libakgl can say which statuses
its own 156 functions raise and what they mean here. A caller writing a `HANDLE` block
needs that, and it is written down nowhere today. **These tables are a required
deliverable, not a nice-to-have.**

Three tables, in chapter 04, with the appendix carrying the full cross-reference.

> **Corrected during execution.** The tables below were written from header prose and were
> wrong in five places; the chapter as built carries the verified version. Recorded here
> because being wrong about the error codes, in the plan that exists to fix wrong
> documentation, is the joke writing itself.
>
> - **`AKGL_ERR_LIMIT` is not a status code.** It is the one-past-the-end sentinel used to
>   compute `AKGL_ERR_COUNT` (261). There are **five** codes, not six: `AKGL_ERR_COUNT` is
>   5, `akgl_error_init` names five, and `tests/error.c` asserts five.
> - **`AKGL_ERR_REGISTRY` is not raised by `akgl_actor_set_character`.** One raise site in
>   the library: `src/controller.c:491`. `akgl_actor_initialize` raises `AKERR_KEY` on a
>   failed registry write; `akgl_actor_set_character` raises `AKERR_NULLPOINTER`.
> - **`AKGL_ERR_BEHAVIOR` is never raised by the library at all** — only by `tests/`.
> - **`akgl_get_json_with_default` does default on `AKERR_OUTOFBOUNDS`**
>   (`src/json_helpers.c:208`, fixed in 0.5.0). The status it deliberately does *not*
>   default on is `AKERR_TYPE` — a missing key can take a default, a malformed one cannot.
> - **Three statuses were missing**: `AKERR_TYPE` (11 raise sites), `AKERR_VALUE` (8),
>   `AKERR_RELATIONSHIP` (1). And **`AKERR_INDEX` is never raised** — it appears only as a
>   `HANDLE_GROUP` arm.

### Table 1 — libakgl's own status codes

All six are offsets from `AKERR_FIRST_CONSUMER_STATUS`, declared in `include/akgl/error.h`
under owner string `AKGL_ERR_OWNER` (`"libakgl"`). The chapter renders this as an aligned
table; the values come in as an `excerpt=include/akgl/error.h` block so the constants
cannot drift from it.

| Code | Value | Means | Typically raised by | What the caller does |
|---|---|---|---|---|
| `AKGL_ERR_SDL` | base + 0 | An SDL call failed; the message carries `SDL_GetError()` | Anything touching a window, texture, renderer or mixer | Usually fatal at startup; check the driver and the asset path |
| `AKGL_ERR_REGISTRY` | base + 1 | A name lookup or registration failed | `akgl_actor_set_character`, registry writes | Check the name and that the asset was loaded *before* the thing referencing it |
| `AKGL_ERR_HEAP` | base + 2 | A pool is exhausted | every `akgl_heap_next_*` | **Normally a missing release, not a small pool.** Raise the `AKGL_MAX_HEAP_*` override only after checking |
| `AKGL_ERR_BEHAVIOR` | base + 3 | A call was made in a state that forbids it | lifecycle and ordering violations | Fix the call order; see the startup sequence in chapter 07 |
| `AKGL_ERR_LOGICINTERRUPT` | base + 4 | **Not a failure — a control signal.** "Skip the rest of this tick for this actor" | your own `movementlogicfunc` | Raise it deliberately; `akgl_physics_simulate` swallows it. A backend's `gravity`/`move` must **never** raise it — there it aborts the whole step |
| `AKGL_ERR_LIMIT` | base + 5 | A fixed compile-time bound was exceeded | loaders hitting `AKGL_*_MAX_*` | Reduce the asset, or raise the bound and rebuild *everything* linking libakgl |

### Table 2 — libakerror statuses libakgl raises, and what they mean *here*

The statuses themselves are libakerror's; their libakgl meaning is not documented
anywhere. This table is what lets a caller write a `HANDLE` block with confidence.

| Status | What it means when a libakgl function raises it |
|---|---|
| `AKERR_NULLPOINTER` | A required pointer argument was `NULL`, or a required field (`akgl_game.name`/`.version`/`.uri`) was empty. Also what `akgl_text_rendertextat` raises for an empty string — while `akgl_text_measure` accepts one |
| `AKERR_KEY` | **A key is absent.** The idiomatic "optional thing was not there" status: a missing JSON key, a character with no sprite for a state, a map with no properties. Frequently a `HANDLE` block rather than a failure |
| `AKERR_INDEX` | An array index was out of range |
| `AKERR_OUTOFBOUNDS` | A value did not fit its destination — `aksl_strncpy` truncation, a frame id past `uint8_t`, a flood fill past `AKGL_DRAW_MAX_FLOOD_SPANS`. **Note:** `akgl_get_json_with_default` does *not* default on this one, which is why it cannot currently give an array element a default |
| `AKERR_IO` | A read or write failed. Distinct from `AKERR_EOF` — that separation is the whole reason `aksl_fgetc` exists |
| `AKERR_EOF` | End of input. Sometimes the desired outcome, as in `require_at_eof` in `src/game.c` |
| `AKERR_API` | The function is not implemented. Currently `akgl_physics_arcade_collide` and `akgl_render_2d_draw_mesh` — both reached by ordinary-looking calls, so both get a chapter callout |

### Table 3 — the exit-status trap

Not a status list; a table because the failure is arithmetic and silent.

| You write | Wait status the shell sees | Why |
|---|---|---|
| `exit(AKGL_ERR_SDL)` | **0 — a clean run** | An exit status is one byte; libakgl's band starts at 256 |
| `akerr_exit(status)` | 0→0, 1–255→itself, else 125 | `AKERR_EXIT_STATUS_UNREPRESENTABLE` |

Every suite in `tests/` once reported success on the most common failure a library built
on SDL can have. This belongs in the manual because a reader writing their own `main` will
write the first line.

### libakgl-specific traps that are ours to document

Not the protocol — the places libakgl makes the protocol bite in a way libakerror's docs
cannot anticipate:

- **`akgl_error_init()` must run before anything that can raise**, or every libakgl error
  prints as "Unknown Error". `akgl_game_init` does it first; a host with its own startup
  path must too.
- **`akgl_registry_iterate_actor` is an SDL callback returning `void`** that ends in
  `FINISH_NORETURN`, so libakerror's default unhandled-error handler **exits the process**.
  A reader meets this the first time a sprite name is wrong.
- **`akgl_get_json_with_default` hands back the context it was given** when it does not
  handle the status, so a `CLEANUP` that also releases it double-releases and corrupts the
  failure instead of reporting it. `AGENTS.md` documents the correct shape; the chapter
  shows it as a verified example because a reader will hit it writing their first loader.
- **libakerror ≥ 2.0.1 is a hard floor**, enforced by two `#error` feature tests in
  `include/akgl/error.h`.
- **`akgl.pc` names no dependencies at all** — no `Requires:` — despite `akerror.h` being
  part of libakgl's public interface. Chapter 03 says so and gives the flags to add by
  hand. (`TODO.md`.)

---

## Part 1 — The verification harness

Ported from `akbasic/tests/docs_examples.sh` (550 lines, bash + awk, no third-party deps).
The `c` / `excerpt` / `sh` / `output` / `norun` / `text` machinery is generic and transfers
directly; what drops out is everything about running an interpreter, and what gets added is
the ability to link and run, because libakgl's samples are C rather than BASIC.

**New file: `tests/docs_examples.sh`.** Same contract, and these house conventions are
worth copying verbatim rather than reinventing:

- **Exit status is the number of failed examples**; `2` for usage/setup errors.
- **A fence with no info string is a hard error**, and so is an unknown one. The failure
  mode the whole harness exists to prevent is passing because it quietly ran nothing.
- Paths forced absolute, because the script `cd`s into sandboxes.
- An unreadable named document is `exit 2` — akbasic learned this when an empty generator
  expression contributed an empty argument that replaced the entire document list, and the
  suite passed having checked nothing.
- A closing census line every run, treated as part of the result.
- `FAIL <doc>:<line>: message` on stderr; output mismatches diffed through `cat -A`,
  because the bugs it catches are trailing spaces and missing newlines.

### Block kinds

| Info string | What happens |
|---|---|
| ```` ```c ```` | `cc -fsyntax-only -std=gnu99 -Wall -Werror` with the project include path |
| ```` ```c wrap=NAME ```` | The same, wrapped in `tests/docs_preludes/NAME.pre` / `.post` |
| ```` ```c run=NAME ```` | **New.** Compile, link against `akgl`, run headless; stdout compared to a following ```` ```output ```` block |
| ```` ```c excerpt=PATH ```` | Must appear verbatim in `PATH` (comment- and whitespace-insensitive); not compiled |
| ```` ```c screenshot=NAME ```` | **New.** Linked against the figure host; renders `docs/images/NAME.png` |
| ```` ```json kind=KIND ```` | **New.** Loaded through the real loader: `sprite`, `character`, `tilemap`, `properties` |
| ```` ```output ```` | Claimed by the preceding runnable block; an unclaimed one is a failure |
| ```` ```sh ```` / `sh norun` | Run in a sandbox and must exit 0 / shown only |
| ```` ```cmake ```` , ```` ```text ```` , `norun` | Never executed; counted as skipped, so the decision is explicit |

`-Werror` on snippets, unlike the library: `AGENTS.md` keeps `AKGL_WERROR` off by default
precisely because libakgl is consumed with `add_subdirectory` and a new compiler's
diagnostic should not break someone else's build. A doc snippet is not a consumer, and a
sample that warns is a sample that teaches the warning. `gnu99` rather than `c99`, for
akbasic's reason — `akerror.h` uses `PATH_MAX`, which `<limits.h>` hides under
`__STRICT_ANSI__`.

`#line` directives are emitted before each body, so a diagnostic reads
`docs/14-physics.md:112: error: ...` rather than pointing into a scratch file.

### Three additions akbasic does not have

**`c run=NAME`** is the reason to do this at all for a game library. `-fsyntax-only` proves
a call typechecks; it does not prove the startup order works, that a sprite loads, or that
an `ATTEMPT` block releases what it acquired. A `run` block links a real binary and
executes it under `SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy SDL_RENDER_DRIVER=software`
— the same forcing `scripts/memcheck.sh` already uses. It asserts a clean exit **and**
greps stderr for a raised context, because a libakgl program can fail and still exit 0.

**`json kind=`** exists because the asset formats are documented in prose and read by
`src/sprite.c`, `src/character.c`, `src/tilemap.c` and `src/registry.c`, with nothing tying
the two together. That gap is not hypothetical: `util/assets/littleguy.json` is already
invalid against the loader it ships with. A small `tools/docs_checkjson.c` writes the block
to a temp file and calls the matching `akgl_*_load_json`, so the documented format is the
format the loader accepts.

**`c screenshot=NAME`** — `tools/docs_screenshot.c` supplies `main`, brings the library up
headless at a stated size, calls the snippet's `docs_frame(void)`, then
`SDL_RenderReadPixels` and writes the PNG via SDL3_image. Copy two of akbasic's decisions
exactly: **any stdout during a render is a failure** (an image of a blank screen is worse
than no image), and **`--check` compares and never repairs** (a test that fixes what it
measures passes the second time for the wrong reason).

Regeneration is `cmake --build build --target docs_screenshots`, never part of `all`.
Figures are tracked, and `docs/images/README.md` says they are generated — the same
tracked-generated-artifact contract `AGENTS.md` already spells out for
`SDL_GameControllerDB.h`.

### Support files and CMake wiring

```
tests/docs_examples.sh          the verifier
tests/docs_preludes/*.pre/.post akglbody, akglfile, akglapp, akglframe
tests/docs_setups/*.sh          asset fixtures a chapter should not have to show
tools/docs_checkjson.c          the json kind= validator
tools/docs_screenshot.c         the figure host
tools/docs_screenshots.sh       figure generation and --check
docs/images/                    tracked, generated
```

Include paths come from a `file(GENERATE)`'d `docs_cflags.txt`, so the transitive path
through `akerror`, `akstdlib`, SDL3 and jansson has one source of truth. Register
`docs_examples` and `docs_screenshots` with `add_test`, `WORKING_DIRECTORY` at the source
root — and note `CMakeLists.txt:76-102` shadows `add_test` only when top-level, so follow
the file's existing idiom rather than calling the builtin.

Follow akbasic's CI decision: **no docs-path filter.** Documentation goes stale because the
code moved, not because somebody edited a chapter.

A prelude must never let a wrong example compile. A prelude declaring `akgl_game_init`
itself would defeat the check.

---

## Part 2 — Chapter structure

`docs/README.md` is a hand-maintained two-column TOC, matching akbasic's conventions:
`NN-kebab-case.md`, H1 repeating the ordinal, no front matter, H2/H3 only (no H4), relative
links between chapters.

| | |
|---|---|
| `01-introduction.md` | What libakgl is, what it refuses to be, the 0.03 ms frame budget from `PERFORMANCE.md`, and the dependency map — who owns which documentation |
| `02-design-philosophy.md` | Pools not `malloc`, backends not branches, errors that carry context, bit flags, name-based registries |
| `03-getting-started.md` | `add_subdirectory` vs `akgl.pc` (and the missing `Requires:`), the first window |
| `04-errors.md` | **The three status tables above.** Protocol referenced, not restated; libakgl's own traps; one worked call sequence |
| `05-the-heap.md` | The five pools, `akgl_heap_next_*`, `akgl_String`, the refcount asymmetry, `AKGL_MAX_HEAP_*` as an ABI constraint |
| `06-the-registry.md` | The eight registries, configuration properties, the id-0 silent-no-op, key truncation |
| `07-the-game-and-the-frame.md` | Startup order, `akgl_game_update`, the state lock, iterators, what savegames do and do not do |
| `08-rendering.md` | `akgl_RenderBackend`, the frame contract, cameras, and **embedding via `akgl_render_2d_bind`** |
| `09-drawing.md` | `draw.h` primitives, colour-as-argument, regions, flood fill's reentrancy limit |
| `10-spritesheets-and-sprites.md` | Sheet sharing by resolved path, the sprite JSON format, animation |
| `11-characters.md` | State→sprite mappings, speeds and accelerations, the character JSON format |
| `12-actors.md` | The 32-bit state mask, the six behaviour hooks, parents and children, layers |
| `13-tilemaps.md` | libakgl's Tiled **extensions and limits** — actor objects, `physics.model`, the perspective band. Format itself referenced |
| `14-physics.md` | thrust/environmental/velocity, `null` and `arcade`, what is not implemented |
| `15-input.md` | Control maps, push-not-poll dispatch, the keystroke ring, the gamepad DB |
| `16-text-and-fonts.md` | The font registry, measuring, the teardown ordering trap |
| `17-audio.md` | The three-voice synthesizer (ours, in full), and the SDL_mixer asset path (referenced) |
| `18-utilities.md` | Collision helpers, path resolution, `akgl_get_json_*` status semantics, static strings |
| `19-tutorial-sidescroller.md` | The first game, start to finish |
| `20-tutorial-jrpg.md` | The second game, start to finish |
| `21-appendix-limits.md` | **Status cross-reference** (which functions raise what), every `AKGL_MAX_*`, the full configuration property table |

Chapter 04 precedes every subsystem because all 156 functions return
`akerr_ErrorContext AKERR_NOIGNORE *`, and a reader who has not met the tables cannot read
a single example. Chapter 08 carries the embedding seam as a first-class topic rather than
a footnote — `akgl_render_2d_bind` is what akbasic actually consumes.

Constant tables (`AKGL_MAX_HEAP_*`, the actor state bits, the iterator ops, the status
codes) go in as `excerpt=` blocks against their headers. Those headers hand-align their
bit-flag tables, and `scripts/reindent.el` deliberately avoids `tabify` to preserve that
alignment — an excerpt keeps the alignment and the values honest at once.

`README.md` keeps its developer-process half and gains a link to `docs/`. Its FAQ half is
deleted, not copied: corrected content lives in the chapters, and one source of truth per
topic is the entire point of the exercise.

---

## Part 3 — The two tutorial games

Real targets: `examples/sidescroller/` and `examples/jrpg/`, each a complete program, built
by default and exercised in CI by a headless smoke run (*run N frames, exit 0*), so a
tutorial cannot silently stop working.

The tutorial chapters quote these programs with ```` ```c excerpt=examples/... ```` blocks
rather than restating the code. A chapter then *cannot* drift from a program that builds —
the excerpt check fails the moment the source moves.

**`examples/sidescroller/`** — gravity, a jump, platforms from a Tiled map, a coin pickup,
a hazard. Exercises arcade physics, `physics.gravity.y`, `AKGL_ACTOR_STATE_MOVING_*`,
tilemap layers, and a custom `movementlogicfunc` (which is where `AKGL_ERR_LOGICINTERRUPT`
stops being a table row and becomes something the reader writes).

This game forces the chapter to make three honest statements, all from `TODO.md`:

- **`akgl_physics_arcade_collide` is not implemented** — it raises `AKERR_API`, and
  `akgl_physics_simulate` never calls `collide` at all. `arcade_move` does no clamping and
  consults no tilemap: an actor walks through a wall and off the edge of the world. The
  tutorial implements its own collision in `movementlogicfunc` and says exactly why.
- **There is no terminal velocity.** Gravity accumulates into `ey` unbounded — the physics
  sim reaches 560 px/s in 0.7 s and keeps going. `physics.drag.y` is the only brake (`ey`
  approaches `gravity_y / drag_y`) and is not documented as such anywhere today.
- **Releasing a direction stops the actor dead.** `akgl_actor_cmhf_*_off` zeroes `tx`/`ty`
  and there is no friction or deceleration. Correct for Zelda, wrong for Mario; the chapter
  shows the workaround rather than pretending.

**`examples/jrpg/`** — four-direction walking with per-facing animation, a Tiled town map,
NPCs spawned from map objects, a text box, and a party member as a child actor. Exercises
null-gravity arcade physics, the full four-way state mask, characters with per-facing
sprite mappings, actors auto-created from object layers, `akgl_text_*`, parent/child actors
(children are snapped to the parent and never simulated — a documented behaviour this game
depends on).

The two are deliberately complementary: the sidescroller is the physics tutorial, the JRPG
is the content-pipeline tutorial.

---

## Part 4 — Assets

`docs/tutorials/assets/` holds a curated **CC0** subset from Kenney.nl, with:

- `LICENSE` — the CC0 deed text.
- `PROVENANCE.md` — an aligned table, one row per file: which pack, the source URL, and
  what it was cropped or repacked into.
- `scripts/fetch_tutorial_assets.sh` — refreshes from upstream into a temp directory and
  moves into place only after verifying the fetch and sanity-checking the contents. This is
  exactly the shape `mkcontrollermappings.sh` was *fixed* into for 0.5.0: check curl's
  status, check a plausible minimum, never overwrite a good tracked copy from a failed run,
  exit non-zero. Do not reintroduce the version that silently destroyed its own fallback.

CC0 specifically, not merely "free": a reader who copies a tutorial into their own game
inherits whatever obligation the assets carry, and CC0 carries none.

**Asset contract, fixed up front** so the art and the game code can be built in parallel:
16×16 tiles, 32×32 character frames, sheets counted left-to-right from the top-left as
`akgl_spritesheet_initialize` expects, at most `AKGL_SPRITE_MAX_FRAMES` (16) frames per
animation with frame ids that fit a `uint8_t`. Maps are Tiled TMJ with **embedded**
tilesets, under `AKGL_TILEMAP_MAX_LAYERS` (16) and `AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER`
(128). Actor objects use `"type":"actor"` with a `character` string property and a `state`
**int** property; the string-array form works in character JSON and is *not* accepted here.

> **Corrected during execution.** This contract originally said *external* tileset
> references, quoting `README.md`: *"The engine ONLY supports TilED TMJ tilemaps with
> tileset external references."* That is backwards, and it is one more stale claim of the
> kind this project exists to fix. `"source"` appears nowhere in `src/tilemap.c`; nothing
> in the library ever opens a `.tsj`. `akgl_tilemap_load_tilesets_each`
> (`src/tilemap.c:139-159`) reads `columns`, `firstgid`, `tilecount` and `image` directly
> off each element of the map's `tilesets` array, and `tests/assets/testmap.tmj` is
> embedded. An external stub fails with `AKERR_KEY` on the missing `columns`.
>
> Four further constraints verified against the loader, all of which bind the tutorial maps:
>
> - **Every object in an object group needs a `type` string**, plain rectangles included.
>   A missing `type` fails the whole load.
> - **Perspective markers need `"type": "perspective"`**, not merely the name
>   `p_foreground`/`p_vanishing`. The loader checks the type first and silently ignores
>   the object otherwise.
> - **A literal 512×512 map is rejected.** Both bounds use `>=`, so 262,144 cells is one
>   too many; 512×511 is the largest that loads.
> - **Tileset images resolve through `akgl_path_relative`** (canonicalized, absolute paths
>   work) but **image-layer images resolve through a plain `"%s/%s"` join** (absolute paths
>   do not). Keep every image path relative to the map file.

### A defect found while planning

`tests/assets/World_A1.png` and `util/assets/Actor1.png` carry the default filenames of
RPG Maker's bundled art and have **no license file**, while `tests/assets/akgl_test_mono.ttf`
sits beside `akgl_test_mono.LICENSE.txt`. RPG Maker's bundled assets are licensed to users
of that product; redistributing them inside a C library is not something that license
covers.

This plan does not fix it — replacing test fixtures is a separate change with its own blast
radius, and it is not blocking the docs. It does two things: the tutorial assets get a clean
provenance story that does not depend on those files, and the finding goes into `TODO.md`
with file, functional consequence and blast radius, per the house practice of documenting
defects against yourself.

---

## Part 5 — How the work is split across subagents

Wave 1 runs entirely in parallel. Nothing waits on `docs/` existing, because the asset
contract, the block grammar and the status tables are all pinned above — that is what lets
the tutorial work start immediately rather than queueing behind the manual.

| Agent | Deliverable | Depends on |
|---|---|---|
| **A — harness** | `tests/docs_examples.sh`, preludes, setups, `tools/docs_checkjson.c`, the screenshot pair, CMake wiring, the harness spec section in `README.md` | nothing |
| **B — assets** | Kenney CC0 subset, `LICENSE`, `PROVENANCE.md`, fetch script, both Tiled maps, sprite/character JSON | the asset contract |
| **C — sidescroller** | `examples/sidescroller/` + chapter 19 | asset contract (B's bytes arrive later) |
| **D — JRPG** | `examples/jrpg/` + chapter 20 | asset contract |
| **E — core** | 04 errors **(owns the three tables)**, 05 heap, 06 registry, 07 game/frame, 21 appendix | nothing |
| **F — presentation** | 08 rendering + embedding, 09 drawing, 10 sprites, 11 characters, 12 actors | nothing |
| **G — world** | 13 tilemaps, 14 physics, 15 input | nothing |
| **H — periphery + front matter** | 16 text, 17 audio, 18 utilities, 01 intro, 02 philosophy, 03 getting started | nothing |

Agent E's status tables are the one wave-1 artifact other agents consume, so E publishes
them first, before writing prose. Every other chapter cites them rather than re-listing
codes locally.

Every chapter agent gets the same five standing instructions, because the failure this
project exists to fix is documentation asserting things nobody checked:

1. **Read `src/`, not the header prose.** The `physics.engine` and `akgl_registry_init`
   claims are both false and have been quoted forward. *"A premise nobody has re-checked is
   where the next defect is hiding."*
2. **Reference upstream, do not restate it.** If the sentence you are writing would still
   be true in a project that does not use libakgl, it belongs in a link. Cite the specific
   upstream document; do not paraphrase it.
3. **Every fence carries an info string.** Prefer `run=`, then `wrap=`, then `excerpt=`; a
   `norun` block is a decision that has to be justified.
4. **Do not restate signatures** — link the Doxygen reference, or `excerpt=` the header.
5. **Note the known defect where a reader would hit it**, with the guard to apply, linked
   to `TODO.md`.

Wave 2 is the integration pass, and the only genuinely serial step: build the TOC, insert
cross-references between chapters and both tutorials, verify no chapter re-teaches upstream
material, reconcile terminology, cut the README's FAQ half, add the `TODO.md` entries, then
run the full gate.

## Verification

```sh
cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
cmake --build build --parallel
ctest --test-dir build --output-on-failure
```

Iterating on one chapter without a full run:

```sh
./tests/docs_examples.sh --root . --cflags-file build/docs_cflags.txt docs/14-physics.md
```

Specific things that must hold before this is done:

- `ctest -R docs_examples` passes, **and its census line accounts for every block**. A
  rising `norun` count is the harness being talked out of its job, and is a review finding.
- `ctest -R docs_screenshots` passes with no figure regenerated.
- **Every status code in `include/akgl/error.h` appears in Table 1**, and every status the
  library actually raises appears in Table 2 — checked by grepping `src/` for `FAIL_*` and
  `AKERR_` status arguments, not by reading the headers.
- Both example games build, and their headless smoke runs exit 0.
- `ctest -R api_surface` and `-R headers` still pass — writing a subsystem chapter tends to
  turn up a symbol declared nowhere, which is precisely what `api_surface` is for.
- `doxygen Doxyfile` is still clean; `WARN_AS_ERROR = FAIL_ON_WARNINGS` means a header
  touched while documenting must stay fully documented.
- `scripts/reindent.sh --check` is clean — `examples/` and `tools/` are C sources in this
  tree and the pre-commit hook will reindent them.
- `scripts/memcheck.sh -R docs_examples` — the `run=` blocks execute real library code and
  are a genuine new memory-check vehicle, which is the shape `AGENTS.md` asks for ("a new
  path worth checking belongs in a benchmark, where it gets both").
- Every commit names the agent program, model and version as co-author, per `AGENTS.md`.

## Out of scope, deliberately

- Replacing the RPG-Maker-named test fixtures (recorded in `TODO.md` instead).
- Fixing the false claims in `physics.h` and `registry.h`. The chapters document what the
  code *does*; correcting the header comments is a separate commit, since `AGENTS.md`
  requires style and behaviour changes to stay unbundled.
- Implementing `arcade_collide`, terminal velocity, or friction. The tutorials work around
  them and say so.
- Documenting libakerror, libakstdlib, SDL3, jansson or the Tiled format. Linked, not
  restated — see the governing editorial rule.
