# 11. Characters **A character is the reusable template. An actor is the instance.** Everything that is the same for every goblin on the map — top speed, acceleration, how long an animation frame is held, and which sprite to draw for which combination of state bits — lives in one `akgl_Character` and is shared by pointer. A hundred goblins cost one character. Everything that is unique to a particular goblin — where it is, which way it is going, which frame it is on — lives in its `akgl_Actor`. See [Chapter 12](12-actors.md). Characters are pool objects (`akgl_heap_next_character`, 256 slots) published in `AKGL_REGISTRY_CHARACTER` under their name, which is how `akgl_actor_set_character` finds them. `akgl_registry_init` has to have run first. ```text akgl_Character "hero" akgl_Actor "player" +--------------------------------+ +---------------------+ | sx, sy, sz top speeds |<---------| basechar | | ax, ay, az accelerations | borrowed| x, y, z | | speedtime frame dwell (ns) | | state (bitmask) | | state_sprites -----------------+---+ | curSpriteFrameId | +--------------------------------+ | +---------------------+ | SDL_PropertiesID keyed by the DECIMAL state bitmask "18" -> akgl_Sprite *hero standing left "146" -> akgl_Sprite *hero walking left ``` ## The state-to-sprite map is an exact-match lookup `state_sprites` is an `SDL_PropertiesID`. The key is **the decimal spelling of the whole state value** — `SDL_itoa(state, buf, 10)` — and the value is an `akgl_Sprite *`. Two things follow, and both of them shape how you build a character: **1. The key is the entire state word, not a set of bits to match against.** A sprite added for `FACE_LEFT | MOVING_LEFT` is *not* found by a lookup for `FACE_LEFT`. There is no subset fallback, no wildcard, no priority order, and no default sprite. **You need a mapping for every state combination the character can actually be in.** **2. A missing mapping is an error, not a quiet miss.** `akgl_character_sprite_get` raises `AKERR_KEY` — this is the one lookup in the library that treats absence as a failure rather than as a successful answer of "nothing here", on the grounds that an actor with no sprite for its current state cannot be drawn. `*dest` is set to `NULL` alongside the error. ```c #include #include /* * sprite_get treats "no mapping" as AKERR_KEY. Handle the status; do not test * the return value for NULL and hope. */ akerr_ErrorContext *sprite_or_none(akgl_Character *basechar, int state, akgl_Sprite **dest) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, basechar, AKERR_NULLPOINTER, "basechar"); FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "dest"); *dest = NULL; ATTEMPT { CATCH(errctx, basechar->sprite_get(basechar, state, dest)); } CLEANUP { } PROCESS(errctx) { } HANDLE(errctx, AKERR_KEY) { /* No sprite for this exact combination. *dest is already NULL. */ } FINISH(errctx, true); SUCCEED_RETURN(errctx); } ``` `akgl_actor_update` and `akgl_actor_render` both swallow that `AKERR_KEY` themselves — states change faster than art gets drawn, and a missing sprite should not stop the frame — so an under-mapped character shows up as an actor that silently does not draw. That is the symptom to recognize. ### The combinations you will actually need Four facings, moving and standing, alive: eight mappings for the simplest walking character. Written out, with the decimal key each one lands on: | Combination | Decimal key | What it draws | |---|---|---| | `ALIVE` | 16 | standing still, **no facing** — see below | | `ALIVE\|FACE_DOWN` | 17 | standing, facing the camera | | `ALIVE\|FACE_LEFT` | 18 | standing, facing left | | `ALIVE\|FACE_RIGHT` | 20 | standing, facing right | | `ALIVE\|FACE_UP` | 24 | standing, facing away | | `ALIVE\|FACE_DOWN\|MOVING_DOWN` | 1041 | walking towards the camera | | `ALIVE\|FACE_LEFT\|MOVING_LEFT` | 146 | walking left | | `ALIVE\|FACE_RIGHT\|MOVING_RIGHT` | 276 | walking right | | `ALIVE\|FACE_UP\|MOVING_UP` | 536 | walking away | **Do not skip the first row.** The default `facefunc` (`akgl_actor_automatic_face`) clears every facing bit and then sets one only if a movement bit is set — so an actor that stops moving is left with *no* facing bit at all, and its state drops to bare `ALIVE`. Without a mapping for 16 it vanishes the moment it stops. This is a known rough edge; the implementation carries a `TODO` saying as much, and [Chapter 12](12-actors.md) covers the workaround. The decimals are shown to make the mechanism concrete. **Write the constants**, or the state-name strings in JSON — never the numbers. ## Adding a mapping `akgl_character_sprite_add` takes a reference on the sprite, so the character keeps it alive. The reference is taken *before* the property write, so a failed write cannot leave the sprite bound with no reference behind it. Rebinding a state that is already mapped releases the sprite it displaced. **The header says otherwise** — `character.h`'s `@note` on `akgl_character_sprite_add` claims the displaced sprite's reference "is never given back". That was true and is not; `src/character.c` reads the old binding out first and releases it, with a comment explaining that without it a character rebinding a state while alive leaks one sprite slot per rebind. > **A defect the release path does have.** The release is guarded by > `(displaced != NULL) && (displaced != ref)`. Re-adding the *same* sprite for the > *same* state therefore increments its reference count and releases nothing — one > leaked reference per redundant call. It is idempotent-looking and is not. > Harmless in a loader that runs once; not harmless in a loop. State `0` is accepted and is a usable key. ## Building a character in code ```c #include #include #include #include akerr_ErrorContext *build_hero(akgl_Sprite *idle_left, akgl_Sprite *walk_left) { akgl_Character *hero = NULL; PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, idle_left, AKERR_NULLPOINTER, "idle_left"); FAIL_ZERO_RETURN(errctx, walk_left, AKERR_NULLPOINTER, "walk_left"); ATTEMPT { CATCH(errctx, akgl_heap_next_character(&hero)); /* Zeroes the struct, creates the map, binds the two hooks, registers. */ CATCH(errctx, akgl_character_initialize(hero, "hero")); /* Everything numeric is left at zero for us. */ hero->speedtime = 100 * AKGL_TIME_ONEMS_NS; /* 100 ms, in nanoseconds */ hero->sx = 0.20f; hero->sy = 0.20f; hero->ax = 0.20f; hero->ay = 0.20f; /* One mapping per exact combination we intend to draw. */ CATCH(errctx, hero->sprite_add( hero, idle_left, AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_FACE_LEFT) ); CATCH(errctx, hero->sprite_add( hero, walk_left, AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_FACE_LEFT | AKGL_ACTOR_STATE_MOVING_LEFT) ); } CLEANUP { } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); } ``` `sprite_add` and `sprite_get` are function pointers on the struct, bound to `akgl_character_sprite_add` and `akgl_character_sprite_get` by `akgl_character_initialize`. Call through the pointers, as with the render backend in [Chapter 08](08-rendering.md) — that is what lets a game substitute its own lookup, a state machine with fallbacks for instance, on one character. `az` and `sz` are **not** read from JSON. They stay 0 unless you set them by hand. ## The character JSON format ```json kind=character setup=character { "name": "hero", "speedtime": 100, "speed_x": 0.20, "speed_y": 0.20, "acceleration_x": 0.20, "acceleration_y": 0.20, "sprite_mappings": [ { "state": [ "AKGL_ACTOR_STATE_ALIVE", "AKGL_ACTOR_STATE_FACE_LEFT" ], "sprite": "hero standing left" }, { "state": [ "AKGL_ACTOR_STATE_ALIVE", "AKGL_ACTOR_STATE_FACE_LEFT", "AKGL_ACTOR_STATE_MOVING_LEFT" ], "sprite": "hero walking left" } ] } ``` | Key | Type | Meaning | |---|---|---| | `name` | string | Registry key in `AKGL_REGISTRY_CHARACTER`. | | `speedtime` | integer | **Milliseconds** a frame is held, scaled to nanoseconds by `AKGL_TIME_ONEMS_NS`. | | `speed_x`, `speed_y` | number | Top speed per axis. Copied onto an actor as `sx`/`sy`. | | `acceleration_x`, `acceleration_y` | number | Acceleration per axis, signed by direction each step. | | `sprite_mappings` | array of objects | One entry per state combination. | | `…[].sprite` | string | A name already in `AKGL_REGISTRY_SPRITE`. | | `…[].state` | array of strings | State names, **OR-ed together** into one key. | All of them are required. An absent key is `AKERR_KEY` naming it; a wrong type is `AKERR_TYPE`. `character.h`'s `@brief` describes `speedtime` as "in seconds". It is milliseconds — the struct field's own documentation says so and `src/character.c` multiplies by `AKGL_TIME_ONEMS_NS`. **Sprites must be loaded first.** This loads characters, not sprites: every name in a `sprite` field has to already be in `AKGL_REGISTRY_SPRITE`, so `akgl_sprite_load_json` runs before `akgl_character_load_json`. A mapping naming an unregistered sprite fails with `AKERR_NULLPOINTER` (not `AKERR_KEY` — it is a lookup miss reported with a pointer status), and the message names the character, the state and the sprite. Two keys you may see in existing files and should not copy: `movementspeed` and `velocity_x`. **Neither is read by anything.** They are ignored, silently. ### State names are prefixed The strings come from `AKGL_ACTOR_STATE_STRING_NAMES` in `src/actor_state_string_names.c`, which `akgl_registry_init_actor_state_strings` turns into a registry. Entry `i` names the bit `1 << i`, and **every name carries the `AKGL_ACTOR_STATE_` prefix**: ```text AKGL_ACTOR_STATE_FACE_DOWN AKGL_ACTOR_STATE_MOVING_LEFT AKGL_ACTOR_STATE_FACE_LEFT AKGL_ACTOR_STATE_MOVING_RIGHT AKGL_ACTOR_STATE_FACE_RIGHT AKGL_ACTOR_STATE_MOVING_UP AKGL_ACTOR_STATE_FACE_UP AKGL_ACTOR_STATE_MOVING_DOWN AKGL_ACTOR_STATE_ALIVE AKGL_ACTOR_STATE_MOVING_IN AKGL_ACTOR_STATE_DYING AKGL_ACTOR_STATE_MOVING_OUT AKGL_ACTOR_STATE_DEAD AKGL_ACTOR_STATE_UNDEFINED_13 .. _31 ``` A name that is not in that list is `AKERR_KEY`, "Unknown actor state", quoting it — a typo is refused rather than skipped, because a skipped typo would bind a sprite to the wrong combination and show up as art that never appears. An empty `state` array is legal and gives you the key `0`. Adding a new state means adding a bit in `include/akgl/actor.h` **and** its name at the matching index in `src/actor_state_string_names.c`. A bit whose name is missing cannot be referred to from JSON at all — which is exactly what happened to `MOVING_IN` and `MOVING_OUT` until 0.5.0. > ### `util/assets/littleguy.json` is invalid against the current loader > > The sample data for the one shipped demo does not load, and it is worth naming > the three separate reasons because each is a different class of drift: > > 1. **The state names are unprefixed** — `"ACTOR_STATE_ALIVE"` rather than > `"AKGL_ACTOR_STATE_ALIVE"`. Every one fails with `AKERR_KEY`, "Unknown actor > state". > 2. **It has `velocity_x` and `velocity_y`**, which nothing reads, and lacks > `speed_x` and `speed_y`, which are required. > 3. **It has no `speedtime`, `acceleration_x` or `acceleration_y`** either — all > three required, all three `AKERR_KEY`. > > `tests/assets/testcharacter.json` is the file to copy from. Fixing the sample is > out of scope here (`plan.md`, "Out of scope, deliberately"); the finding is > recorded so nobody quotes it forward. ## Teardown `akgl_heap_release_character` decrements the reference count, and at zero it walks `state_sprites` with `akgl_character_state_sprites_iterate`, releasing every sprite reference `sprite_add` took, destroys the property set, clears the registry entry and zeroes the slot. That walk is an `SDL_EnumerateProperties` callback, so it returns `void` and ends in `FINISH_NORETURN`. **An error inside it exits the process** via libakerror's default unhandled-error handler. The same hazard, in its more common form, is in [Chapter 12](12-actors.md) under `akgl_registry_iterate_actor`; install your own `akerr_handler_unhandled_error` if a game should survive it. An actor **borrows** its character and takes no reference on it. Releasing a character out from under a live actor leaves a dangling `basechar`. ## Known defects worth knowing here - **Re-adding the same sprite for the same state leaks a reference.** See above. - **Long names register and unregister under different keys.** `akgl_character_initialize` writes the registry entry under the *caller's* `name` pointer, while `akgl_heap_release_character` clears it under the character's own truncated 128-byte copy. For a name over 127 bytes those differ, and the registry entry survives the release — pointing at a zeroed slot. Latent for ordinary names; related to `TODO.md`, "Truncated registry keys can collide", which covers the collision half. - **`speedtime` is written through an `int *` cast of a `uint64_t` field.** `src/character.c` passes `(int *)&obj->speedtime` to `akgl_get_json_integer_value`. It works because the struct was zeroed and the platform is little-endian; it is the same class of cast-over-a-signature-mismatch that `AGENTS.md` argues against under "Never silence a warning with a cast". - **`akgl_character_initialize` silently replaces a same-named character**, and the one it displaced becomes unreachable rather than being released. ## Where to look next - [Chapter 10](10-spritesheets-and-sprites.md) — the sprites these map to. - [Chapter 12](12-actors.md) — the state bitmask itself, and what sets it. - [Chapter 14](14-physics.md) — what `sx`/`sy` and `ax`/`ay` mean once an actor is simulated.