Files
libakgl/docs/tutorials/assets/PROVENANCE.md
Andrew Kesterson 88aaa184e4 Add two tutorial games that build, run in CI, and cannot drift
examples/sidescroller and examples/jrpg are complete programs, built with the
library and exercised headless by ctest. The chapters quote them with
`c excerpt=examples/...` blocks rather than restating the code, so a chapter
cannot drift from a program that compiles -- the excerpt check fails the moment
the source moves. 34 excerpts in one chapter, 21 in the other.

The two are complementary. The sidescroller is the physics tutorial: gravity, a
jump, coins, hazards. The JRPG is the content-pipeline tutorial: a town map,
NPCs spawned from map objects, four-way per-facing animation, a text box, a
follower.

Both smoke tests drive real SDL_Events through akgl_controller_handle_event and
step the physics clock at a fixed 1/60s rather than sleeping, so a scripted run
is deterministic and finishes in under five seconds.

Writing them is what turned up most of the defects recorded in the next commit,
because a game exercises paths a unit test does not. Each workaround says in the
chapter which library gap forced it:

- collision is written in a custom movementlogicfunc, because
  akgl_physics_arcade_collide raises AKERR_API and akgl_physics_simulate never
  calls collide at all;
- the sidescroller cancels the step's own gravity when it blocks downward,
  because otherwise a quarter-pixel of penetration makes the *horizontal* sweep
  report blocked and the character walks backwards a tile at a time;
- both clear movement_controls_face on every map-spawned actor, because the
  default facefunc leaves a stopped actor with no facing bit, no sprite, and no
  draw;
- the JRPG's follower gets a renderfunc that nulls obj->parent for the duration
  of the draw, because a child's offset is counted twice.

Assets are CC0 from three Kenney packs, vendored with per-pack licence text,
per-file provenance, and the geometry contract in
docs/tutorials/assets/README.md. CC0 specifically rather than merely free: a
reader who copies a tutorial into their own game inherits no obligation.
scripts/fetch_tutorial_assets.sh refreshes them in the shape
mkcontrollermappings.sh was fixed into for 0.5.0 -- it checks curl's status,
refuses a pack page that does not say CC0, verifies the archive and the staged
dimensions, and leaves the tracked bytes untouched on any failure. Both failure
paths were tested, and a no-op refresh is byte-identical.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 20:59:00 -04:00

15 KiB

Provenance of the tutorial assets

Everything under docs/tutorials/assets/ is CC0 1.0 or was written for this repository. Nothing here carries an attribution requirement, a share-alike clause, or a field-of-use restriction, which is the whole point: a reader who copies a tutorial into their own game inherits whatever obligation these assets carry, and CC0 carries none. "Free to download" is not the same thing and was not accepted -- each pack's licence was read on its Kenney page and in the License.txt it ships, and scripts/fetch_tutorial_assets.sh re-checks both on every refresh and refuses to vendor a pack that fails either.

LICENSE is the CC0 1.0 legal code. The three LICENSE.kenney_*.txt files are the licence statements the packs themselves ship, kept beside the art in the same way tests/assets/akgl_test_mono.LICENSE.txt sits beside its font.

The two tables below account for all 56 asset files: 14 carry upstream bytes and 42 were written here. This file and README.md are the remaining two, and are documentation of the same kind as the rest of docs/.

Upstream packs

Pack URL Licence Licence file in this tree
Pixel Line Platformer https://kenney.nl/assets/pixel-line-platformer CC0 1.0 LICENSE.kenney_pixel-line-platformer.txt
RPG Urban Pack https://kenney.nl/assets/rpg-urban-pack CC0 1.0 LICENSE.kenney_rpg-urban-pack.txt
Music Jingles https://kenney.nl/assets/music-jingles CC0 1.0 LICENSE.kenney_music-jingles.txt

Kenney asks for credit and does not require it. Credit is given here and in the tutorial chapters.

Files carrying upstream bytes

File Pack Source URL Licence Cropped / repacked into
LICENSE creativecommons.org https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt CC0 1.0 The CC0 1.0 Universal legal code, fetched verbatim
LICENSE.kenney_pixel-line-platformer.txt Pixel Line Platformer https://kenney.nl/assets/pixel-line-platformer CC0 1.0 The pack's own License.txt, copied verbatim
LICENSE.kenney_rpg-urban-pack.txt RPG Urban Pack https://kenney.nl/assets/rpg-urban-pack CC0 1.0 The pack's own License.txt, copied verbatim
LICENSE.kenney_music-jingles.txt Music Jingles https://kenney.nl/assets/music-jingles CC0 1.0 The pack's own License.txt, copied verbatim
sidescroller/tiles.png Pixel Line Platformer https://kenney.nl/assets/pixel-line-platformer CC0 1.0 Tilemap/tilemap_packed.png copied verbatim: 160x96, 10x6 tiles of 16x16, no spacing, no margin
sidescroller/player.png Pixel Line Platformer https://kenney.nl/assets/pixel-line-platformer CC0 1.0 Tiles 40, 41, 42 and their horizontal mirrors, bottom-centred in six 32x32 cells
sidescroller/coin.png Pixel Line Platformer https://kenney.nl/assets/pixel-line-platformer CC0 1.0 Tile 44, bottom-centred in one 32x32 cell
sidescroller/hazard.png Pixel Line Platformer https://kenney.nl/assets/pixel-line-platformer CC0 1.0 Tiles 55, 56, 51, 52, bottom-centred in four 32x32 cells
sidescroller/jingle_start.ogg Music Jingles https://kenney.nl/assets/music-jingles CC0 1.0 Audio/8-Bit jingles/jingles_NES00.ogg, copied verbatim (1.8 s)
jrpg/tiles.png RPG Urban Pack https://kenney.nl/assets/rpg-urban-pack CC0 1.0 Tilemap/tilemap_packed.png copied verbatim: 432x288, 27x18 tiles of 16x16, no spacing, no margin
jrpg/player.png RPG Urban Pack https://kenney.nl/assets/rpg-urban-pack CC0 1.0 Character block 0 (tileset columns 23-26, rows 0-2) regrouped into twelve 32x32 cells
jrpg/npc_shopkeeper.png RPG Urban Pack https://kenney.nl/assets/rpg-urban-pack CC0 1.0 Character block 3 (tileset columns 23-26, rows 9-11) regrouped into twelve 32x32 cells
jrpg/npc_elder.png RPG Urban Pack https://kenney.nl/assets/rpg-urban-pack CC0 1.0 Character block 2 (tileset columns 23-26, rows 6-8) regrouped into twelve 32x32 cells
jrpg/jingle_start.ogg Music Jingles https://kenney.nl/assets/music-jingles CC0 1.0 Audio/Pizzicato jingles/jingles_PIZZI07.ogg, copied verbatim (1.3 s)

Files written for this repository

These contain no upstream content. They are libakgl source in JSON form: they name the art above, they do not embed it.

File Kind What it is
jrpg/character_jrpg_elder.json character State-name to sprite-name bindings; contains no upstream content
jrpg/character_jrpg_player.json character State-name to sprite-name bindings; contains no upstream content
jrpg/character_jrpg_shopkeeper.json character State-name to sprite-name bindings; contains no upstream content
jrpg/sprite_jrpg_elder_idle_down.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_elder_idle_left.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_elder_idle_right.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_elder_idle_up.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_elder_walk_down.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_elder_walk_left.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_elder_walk_right.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_elder_walk_up.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_player_idle_down.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_player_idle_left.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_player_idle_right.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_player_idle_up.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_player_walk_down.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_player_walk_left.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_player_walk_right.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_player_walk_up.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_shopkeeper_idle_down.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_shopkeeper_idle_left.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_shopkeeper_idle_right.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_shopkeeper_idle_up.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_shopkeeper_walk_down.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_shopkeeper_walk_left.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_shopkeeper_walk_right.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/sprite_jrpg_shopkeeper_walk_up.json sprite Frame list into the sheet named by its own spritesheet.filename
jrpg/town.tmj map Tiled 1.8 TMJ, tileset embedded. 30x20 cells, ground + decoration + actors
sidescroller/character_ss_coin.json character State-name to sprite-name bindings; contains no upstream content
sidescroller/character_ss_hazard_blob.json character State-name to sprite-name bindings; contains no upstream content
sidescroller/character_ss_hazard_moth.json character State-name to sprite-name bindings; contains no upstream content
sidescroller/character_ss_player.json character State-name to sprite-name bindings; contains no upstream content
sidescroller/level1.tmj map Tiled 1.8 TMJ, tileset embedded. 40x15 cells, background + terrain + actors
sidescroller/sprite_ss_coin.json sprite Frame list into the sheet named by its own spritesheet.filename
sidescroller/sprite_ss_hazard_blob.json sprite Frame list into the sheet named by its own spritesheet.filename
sidescroller/sprite_ss_hazard_moth.json sprite Frame list into the sheet named by its own spritesheet.filename
sidescroller/sprite_ss_player_idle_left.json sprite Frame list into the sheet named by its own spritesheet.filename
sidescroller/sprite_ss_player_idle_right.json sprite Frame list into the sheet named by its own spritesheet.filename
sidescroller/sprite_ss_player_jump_left.json sprite Frame list into the sheet named by its own spritesheet.filename
sidescroller/sprite_ss_player_jump_right.json sprite Frame list into the sheet named by its own spritesheet.filename
sidescroller/sprite_ss_player_run_left.json sprite Frame list into the sheet named by its own spritesheet.filename
sidescroller/sprite_ss_player_run_right.json sprite Frame list into the sheet named by its own spritesheet.filename

How the repacking works, and why

Tilesets are copied verbatim. Both packs already ship a 16x16 grid with zero spacing and zero margin, which is the only geometry akgl_tilemap_compute_tileset_offsets gets right -- it adds spacing to the tile pitch but sets the first row's y offset to spacing rather than zero, and it ignores margin completely. A packed sheet with a 1 px gutter, which is what most of Kenney's older packs ship, would render every tile in row 0 one pixel low. That ruled several otherwise-suitable packs out.

Character sheets are repacked into one row of 32x32 cells. The asset contract for these tutorials is 16x16 tiles and 32x32 character frames, and neither pack ships 32x32 art, so each 16x16 source tile is composited at (8, 16) inside its cell -- horizontally centred, sitting on the cell's bottom edge. The art is not resampled: a 2x upscale would put 2x2 pixel blocks next to 1x1 tiles, and every sprite in the repository would stop matching the source pixel for pixel. The cost is that three quarters of each cell is empty, and the benefit is one rule the tutorials can rely on: an actor drawn into a 32x32 destination rectangle has its feet on the bottom edge of that rectangle and its body centred, whichever sheet it came from.

Left-facing frames are stored, not mirrored at draw time. akgl_actor_render calls draw_texture with SDL_FLIP_NONE hard-coded (src/actor.c), so there is no way to ask for a mirrored blit. The sidescroller sheet therefore carries frames 0-2 facing right and frames 3-5 as their horizontal mirrors. The JRPG sheets need no mirroring: the RPG Urban Pack draws both side views.

One frame is used twice. Pixel Line Platformer has no dedicated jump pose. Frame 1 is the passing position of the run cycle, which is the airborne one, and ss_player_jump_right reuses it. Said here rather than left for a reader to notice.

The coin does not animate. Pixel Line Platformer ships one gold pickup tile and no rotation frames, so sprite_ss_coin.json is a single frame. Nothing was invented to pad it out.

Reproducing this

scripts/fetch_tutorial_assets.sh

Fetches all three packs, checks each page and each shipped License.txt says CC0, verifies the source images are the size the frame arithmetic assumes, repacks into a temporary directory, checks every staged file's dimensions, and only then moves anything into place. A failure at any step leaves the tracked copies untouched and exits non-zero. The output is byte-for-byte reproducible -- ImageMagick's wall-clock date:create and date:modify chunks are stripped, so a refresh that changes nothing produces no diff.

The script does not touch the JSON or the TMJ maps. Those are hand-maintained.