# The tutorial asset contract

What `examples/sidescroller/` and `examples/jrpg/` are drawing, and the exact
numbers they have to use. Licensing is in `PROVENANCE.md`; refreshing the art
from upstream is `scripts/fetch_tutorial_assets.sh`.

Every claim below was checked against `src/`, not against header prose, and the
whole set was loaded through the real library -- `akgl_sprite_load_json`,
`akgl_character_load_json`, `akgl_tilemap_load` -- before being committed.

## Geometry

| Thing                   | Value  | Why it is that value                                                      |
|-------------------------|--------|---------------------------------------------------------------------------|
| Tile                    | 16x16  | Both tilesets ship on a 16x16 grid                                        |
| Tileset spacing, margin | 0, 0   | `akgl_tilemap_compute_tileset_offsets` gets nothing else right; see below |
| Character frame         | 32x32  | The fixed contract for these tutorials                                    |
| Art inside a frame      | 16x16  | Composited at (8, 16): centred, sitting on the cell's bottom edge         |
| Frames per sheet row    | all    | Every sheet is a single row, so `coords_for_frame` never has to wrap      |
| Frames per animation    | max 16 | `AKGL_SPRITE_MAX_FRAMES`; a 17th is `AKERR_OUTOFBOUNDS` at load           |
| Frame id                | 0..255 | `uint8_t`; a larger id is `AKERR_OUTOFBOUNDS` at load                     |

**The one alignment rule.** `akgl_actor_render` draws into a rectangle of
`sprite->width` by `sprite->height` with its top-left at the actor's `x`, `y`.
Because every frame's art is bottom-centred in its cell, an actor at `(x, y)`
has its feet at `y + 32` and its 16 px body spanning `x + 8` to `x + 23`. Ground
contact, pickup tests and hitboxes should use that inner rectangle, not the
32x32 cell.

## Sidescroller sheets

`sidescroller/player.png` -- 192x32, six frames.

| Frame | Facing | Pose                            | Used by                                       |
|-------|--------|---------------------------------|-----------------------------------------------|
| 0     | right  | contact                         | `ss_player_idle_right`, `ss_player_run_right` |
| 1     | right  | passing, airborne               | `ss_player_run_right`, `ss_player_jump_right` |
| 2     | right  | opposite contact                | `ss_player_run_right`                         |
| 3     | left   | contact (mirror of 0)           | `ss_player_idle_left`, `ss_player_run_left`   |
| 4     | left   | passing, airborne (mirror of 1) | `ss_player_run_left`, `ss_player_jump_left`   |
| 5     | left   | opposite contact (mirror of 2)  | `ss_player_run_left`                          |

The left-facing frames exist as their own art because `akgl_actor_render` passes
`SDL_FLIP_NONE` to `draw_texture` unconditionally (`src/actor.c`). There is no
way to ask the library for a mirrored blit.

`sidescroller/coin.png` -- 32x32, one frame. The pack ships no rotation frames.

`sidescroller/hazard.png` -- 128x32, four frames.

| Frame | What                     | Used by          |
|-------|--------------------------|------------------|
| 0     | red blob, ground, pose A | `ss_hazard_blob` |
| 1     | red blob, ground, pose B | `ss_hazard_blob` |
| 2     | moth, flying, wings up   | `ss_hazard_moth` |
| 3     | moth, flying, wings down | `ss_hazard_moth` |

## JRPG sheets

`jrpg/player.png`, `jrpg/npc_shopkeeper.png` and `jrpg/npc_elder.png` are
384x32, twelve frames, and share one layout.

| Frames | Facing | Poses                 | Idle sprite uses | Walk sprite uses |
|--------|--------|-----------------------|------------------|------------------|
| 0-2    | down   | stand, step A, step B | 0                | 1, 0, 2, 0       |
| 3-5    | left   | stand, step A, step B | 3                | 4, 3, 5, 3       |
| 6-8    | right  | stand, step A, step B | 6                | 7, 6, 8, 6       |
| 9-11   | up     | stand, step A, step B | 9                | 10, 9, 11, 9     |

The stand frame between the two steps is what makes it read as a walk rather
than a shuffle; it is the same three-frame cycle the source art was drawn for.

## States

A character's sprite map is keyed by `SDL_itoa(state)` and looked up with
`SDL_GetPointerProperty` (`akgl_character_sprite_get`), so **the match is on the
whole integer, not on a mask test**. A state the character has no exact entry
for makes the actor invisible for that frame -- `actor_visible` treats
`AKERR_KEY` as "nothing to draw", which is an answer, not an error.

These are the values the library's own input handlers produce.
`akgl_actor_cmhf_<dir>_on` clears every `FACE_*` and `MOVING_*` bit before
setting its own pair, and `_off` clears only its `MOVING_*` bit, so the facing
survives the key release and no two `MOVING_*` bits are ever set at once.

| State | Bits                                | Sidescroller sprite | JRPG sprite      |
|-------|-------------------------------------|---------------------|------------------|
| 16    | ALIVE                               | (coin, hazards)     | --               |
| 17    | ALIVE, FACE_DOWN                    | `..._idle_right`    | `..._idle_down`  |
| 18    | ALIVE, FACE_LEFT                    | `..._idle_left`     | `..._idle_left`  |
| 20    | ALIVE, FACE_RIGHT                   | `..._idle_right`    | `..._idle_right` |
| 24    | ALIVE, FACE_UP                      | --                  | `..._idle_up`    |
| 146   | ALIVE, FACE_LEFT, MOVING_LEFT       | `..._run_left`      | `..._walk_left`  |
| 276   | ALIVE, FACE_RIGHT, MOVING_RIGHT     | `..._run_right`     | `..._walk_right` |
| 530   | ALIVE, FACE_LEFT, MOVING_UP         | `..._jump_left`     | --               |
| 532   | ALIVE, FACE_RIGHT, MOVING_UP        | `..._jump_right`    | --               |
| 536   | ALIVE, FACE_UP, MOVING_UP           | `..._jump_right`    | `..._walk_up`    |
| 658   | ALIVE, FACE_LEFT, MOVING_LEFT, UP   | `..._jump_left`     | --               |
| 788   | ALIVE, FACE_RIGHT, MOVING_RIGHT, UP | `..._jump_right`    | --               |
| 1041  | ALIVE, FACE_DOWN, MOVING_DOWN       | --                  | `..._walk_down`  |

The four jump states are there because a sidescroller that routes its jump
through `akgl_actor_cmhf_up_on` lands on 536 -- that handler clears `FACE_RIGHT`
and sets `FACE_UP`, which in a side view is not a facing at all. A game that
sets `ey` directly instead never reaches those states, and nothing breaks
either way.

State names in character JSON are the **prefixed** spellings from
`src/actor_state_string_names.c`: `AKGL_ACTOR_STATE_ALIVE`, not
`ACTOR_STATE_ALIVE`. `util/assets/littleguy.json` uses the old unprefixed names
and `velocity_x`, and does not load against the current library. Do not copy it.

## Maps

Both maps are Tiled 1.8 TMJ with the tileset **embedded**.

`akgl_tilemap_load` reads `columns`, `firstgid`, `tilecount`, `image`,
`imagewidth`, `imageheight`, `margin`, `spacing`, `tilewidth`, `tileheight` and
`name` straight out of each element of the map's `tilesets` array
(`akgl_tilemap_load_tilesets_each`). The string `source` appears nowhere in
`src/tilemap.c` and nothing in the library ever opens a `.tsj`, so an external
tileset reference fails with `AKERR_KEY` on the missing `columns`. `README.md`
in the repository root says the opposite; it is wrong.

| Map                       | Cells | Layers                                    | Objects |
|---------------------------|-------|-------------------------------------------|---------|
| `sidescroller/level1.tmj` | 40x15 | background (tile), terrain (tile), actors | 7       |
| `jrpg/town.tmj`           | 30x20 | ground (tile), decoration (tile), actors  | 3       |

Global tile ids are `(row * columns) + column + 1`; `0` means an empty cell and
is skipped. `sidescroller/tiles.png` has ten columns and 60 tiles;
`jrpg/tiles.png` has twenty-seven columns and 486 tiles, the last four columns
of which are the character art the sheets above were cut from. Those cells are
never referenced by `town.tmj`.

Other things the loader insists on, all of them checked:

- **Every object needs a `type` string**, including ones that are not actors. A
  missing `type` fails the whole load, not just that object.
- An actor object needs a non-empty `name`, a `character` **string** property
  and a `state` **int** property. The string-array form of `state` works in
  character JSON and is not accepted here.
- The `character` named must already be in `AKGL_REGISTRY_CHARACTER`, which
  means every sprite and character JSON has to be loaded before the map.
- A map's `properties` are optional; if present, `physics.model` must name a
  backend that exists (`null` or `arcade`) or the load fails. Gravity and drag
  keys are `float`.
- Tileset image paths resolve through `akgl_path_relative` against the map's own
  directory. Image-layer paths do not -- those go through a plain `"%s/%s"`
  join, so an absolute path there does not work. Keep every path relative.

## Physics

The sidescroller map asks for the `arcade` backend with `physics.gravity.y`
900.0 and `physics.drag.y` 1.5. There is no terminal velocity in the backend;
`ey` approaches `gravity_y / drag_y`, so those two numbers set it to 600 px/s.
The JRPG map asks for `arcade` with both gravity components at 0.0.

`ss_player` has `speed_y` and `acceleration_y` of 0.0 on purpose: a zero top
speed on an axis means `akgl_physics_simulate` zeroes that axis's thrust
outright, so holding a vertical direction cannot make the player fly. A jump
belongs in `ey`.
