# 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 the issue that tracks it |

---

## 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. (Issue #17.)

---

## 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 the defect record:

- **`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 the tracker
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 its issue.

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, file the issues the writing turned
up, 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 (issue #52 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.
