Compile, link and run every example in the documentation

libakgl exports 157 functions and the only user-facing documentation was a FAQ
in README.md whose examples did not compile: two unbalanced `PASS()` calls, a
`sprite->frameids = [0, 1, 2, 3];` that is not C in any dialect, a stray `9`
inside a bitmask expression, an `int screenwidth = NULL`, and -- in the first
snippet a reader ever saw -- the exact `strncpy` call AGENTS.md forbids.

That is not a reader routing around typos. It is what happens to samples that
nothing executes. This is akbasic's documentation harness with the interpreter
taken out and the ability to link and run put in.

The contract is a set of fence info strings, so an example is ordinary markdown
that still highlights on the forge:

  ```c                  compiled with -fsyntax-only -Wall -Werror
  ```c wrap=NAME        the same, wrapped in tests/docs_preludes/NAME.pre/.post
  ```c run=NAME         linked against akgl and run headless
  ```c excerpt=PATH     must still appear verbatim in PATH
  ```c screenshot=NAME  also the source of docs/images/NAME.png
  ```json kind=KIND     loaded through the real akgl_*_load_json
  ```output             the exact stdout of the runnable block above it

`run=` is why this links at all: -fsyntax-only proves a call typechecks, not
that the startup order works or that an ATTEMPT block gives back what it took.
`json kind=` exists because the asset formats are documented in prose and read
by four loaders with nothing tying the two together -- util/assets/littleguy.json
is already invalid against the loader it ships with.

A fence with no info string is a hard error, and so is an unknown one. The
failure mode this exists to prevent is passing because it quietly ran nothing,
so an unannotated block is a missing decision rather than a default. Exit status
is the number of failed examples; 2 for a usage error, which is a different
thing and has to be distinguishable.

Proven to fail on all ten of its failure modes -- untagged fence, non-compiling
snippet, stale excerpt, orphan output block, unknown info string, run= output
mismatch, invalid JSON, missing figure, a -Werror warning, and a dangling
preload= -- because a check that has never failed has not been tested.

`-Werror` here although AKGL_WERROR stays off for the library: that option is
off so a new compiler's diagnostic cannot break a consumer's build, and a doc
snippet is not a consumer. A sample that warns is a sample that teaches the
warning.

Registered as `docs_examples` and `docs_screenshots`. No docs-path filter in CI,
deliberately: documentation goes stale because the code moved, not because
somebody edited a chapter.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-01 20:58:11 -04:00
parent 2766372f37
commit 8ac291d2dd
19 changed files with 2271 additions and 0 deletions

68
tests/docs_setups/character.sh Executable file
View File

@@ -0,0 +1,68 @@
#!/bin/bash
#
# Stage the sprites a `json kind=character` block names.
#
# akgl_character_load_json resolves every `sprite` field against
# AKGL_REGISTRY_SPRITE and fails on a name that is not there, so a chapter that
# shows a character file on its own is not runnable on its own. Rather than make
# the chapter print two sprite files it is not about, the sprites live here and
# tools/docs_checkjson.c loads everything in ./sprites through
# akgl_sprite_load_json before it touches the character.
#
# **The two sprite names are a contract with docs/11-characters.md**, which names
# `hero standing left` and `hero walking left` in its `sprite_mappings`. Changing
# either name here breaks that chapter, so change both together.
#
# The sheet is staged inside ./sprites because a sprite file's `spritesheet`
# path is resolved relative to the sprite file's own directory.
#
# $1 is the repository root. The caller runs this with the sandbox as the
# working directory.
set -u
ROOT="${1:-}"
if [ -z "${ROOT}" ]; then
echo "usage: character.sh <repository-root>" >&2
exit 2
fi
mkdir -p sprites || exit 1
cp "${ROOT}/tests/assets/spritesheet.png" sprites/spritesheet.png || exit 1
# 576x384 of 48x48 frames: a 12x8 grid, so frames 0-95 exist. Two frames each,
# which is enough for the animation fields to mean something without making the
# fixture a thing anybody has to study.
cat > sprites/hero_standing_left.json <<'JSON'
{
"spritesheet": {
"filename": "spritesheet.png",
"frame_width": 48,
"frame_height": 48
},
"name": "hero standing left",
"width": 48,
"height": 48,
"speed": 200,
"loop": true,
"loopReverse": false,
"frames": [0]
}
JSON
cat > sprites/hero_walking_left.json <<'JSON'
{
"spritesheet": {
"filename": "spritesheet.png",
"frame_width": 48,
"frame_height": 48
},
"name": "hero walking left",
"width": 48,
"height": 48,
"speed": 100,
"loop": true,
"loopReverse": true,
"frames": [0, 1, 2]
}
JSON