# 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 :: 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 `` 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.