/** @brief Longest object name. Note that an object naming an actor is truncated at #AKGL_ACTOR_MAX_NAME_LENGTH (128) instead. */
#define AKGL_TILEMAP_MAX_OBJECT_NAME_SIZE 512
/** @brief Objects in one object layer. Not enforced by the loader -- a longer group writes past the array. */
#define AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER 128
```
Two of these carry a wrinkle worth knowing before you lay out a level.
**The width and height bound is on the product, and it is exclusive.** `akgl_tilemap_load`
refuses when `width * height >= AKGL_TILEMAP_MAX_WIDTH * AKGL_TILEMAP_MAX_HEIGHT`, so
262,144 cells is one too many: **a literal 512x512 map is rejected with
`AKERR_OUTOFBOUNDS`**, and 512x511 is the largest rectangle that loads. The same
off-by-one applies per tile layer in `akgl_tilemap_load_layer_tile`. Nothing is wrong with
either check other than the `>=`; it is recorded here because "the maximum is 512x512" is
what the constant names say and is not what the code does.
**The doc comment on `AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER` is stale.** It says the loader
does not enforce it. The loader does — `akgl_tilemap_load_layer_objects` checks `j` at the
top of the loop body and raises `AKERR_OUTOFBOUNDS` naming the layer, and
`akgl_tilemap_load_tilesets` does the same against `AKGL_TILEMAP_MAX_TILESETS`. Both were
genuinely unbounded and both were fixed in 0.5.0 (`TODO.md`, "Found while rewriting the
Doxygen comments" item 17), with fixtures in `tests/tilemap.c` at exactly 128, 129 and 17.
Trust the code.
## What the loader accepts
The loader reads a strict subset of the TMJ format. Everything in this section is a
requirement, not a suggestion: a key that is absent raises `AKERR_KEY` and a key with the
wrong JSON type raises `AKERR_TYPE`, both naming what was wanted. See
[Chapter 4](04-errors.md) for what those statuses mean in libakgl.
| Level | Members `akgl_tilemap_load` requires | Notes |
|---|---|---|
| Map root | `tilewidth`, `tileheight`, `width`, `height`, `layers`, `tilesets` | `orientation` is read from nowhere and forced to 0; isometric is not implemented |
| Every layer | `id`, `opacity`, `visible`, `x`, `y`, `type` | A `type` other than the three below keeps these fields and is otherwise skipped |
| `tilelayer` | `width`, `height`, `data` | `data` must be a plain array of integers |
| `imagelayer` | `image` | Layer `width`/`height` are taken from the loaded texture, not the JSON |
| `objectgroup` | `objects` | See below — every object has requirements of its own |
| Every object | `name`, `x`, `y`, `visible`, `type` | **Including plain rectangles.** An object with no `type` member fails the whole load |
| Every tileset | `columns`, `firstgid`, `imagewidth`, `imageheight`, `margin`, `spacing`, `tilecount`, `tilewidth`, `tileheight`, `name`, `image` | The **embedded** form; see below |
Three of these deserve their own paragraph.
**Tilesets must be embedded, not external.** Tiled writes a tileset into the map's
`tilesets` array in one of two shapes: the whole tileset inline, or a two-member stub
`{"firstgid": N, "source": "tiles.tsj"}` pointing at a separate file.
`akgl_tilemap_load_tilesets_each` reads `columns`, `image` and the rest directly out of the
array element and there is no code anywhere in `src/` that opens a `source` file, so **an
external tileset reference fails with `AKERR_KEY` on the missing `columns`.** Turn
*Embed tileset* on when you save, or use Tiled's *Map → Embed Tilesets* command. The
shipped fixture `tests/assets/testmap.tmj` is embedded, which is what makes it load.
**Every object needs a `type` string, and `type` is where the extensions live.** The loader
reads it on every object in an object group and compares it against `"actor"` and
`"perspective"`; anything else is recorded in the layer's object array and otherwise
ignored. An object with the member missing entirely does not reach that comparison — it
fails the load. If your editor writes the object's class under a different member name,
that is the drift to check first; Tiled's own field naming has changed across releases, and
the fixture in this repository is a Tiled 1.8.2 export.
**The map file's directory is the root every path resolves against**, so a map and its art
move together. The two resolvers are not the same, though, and the difference matters:
| Path | Resolved by | Consequence |
|---|---|---|
| A tileset's `image` | `akgl_path_relative` | Canonicalized against the map's directory; an absolute path works |
| An image layer's `image` | `aksl_snprintf("%s/%s", ...)` | A plain join. **Not** canonicalized, so an absolute path becomes `/maps//art/sky.png` and does not open |
Both are bounded at `AKGL_TILEMAP_MAX_TILESET_FILENAME_SIZE` (`PATH_MAX`). The image-layer
join raises `AKERR_OUTOFBOUNDS` naming both lengths when it does not fit; it used to
truncate silently, and an over-long path reported itself as a missing file with a name the
caller never wrote.
## libakgl's three extensions to Tiled
None of these are part of the TMJ format. They are conventions the loader imposes on
custom properties and object types, and Tiled will happily author all three without
knowing what they mean.
### 1. Object type `actor` spawns a real actor
An object whose `type` is the string `"actor"` does not stay a rectangle. The loader
claims an actor from the pool, initializes it, binds a character, publishes it in
`AKGL_REGISTRY_ACTOR`, and copies the object's position, visibility and layer index onto
it. The object's `name` is the registry key. See [Chapter 12](12-actors.md) for what an
actor is and [Chapter 11](11-characters.md) for what a character supplies.
Two custom properties are required on the object:
| Property | Tiled type | Meaning |
|---|---|---|
| `character` | `string` | Name of a character already in `AKGL_REGISTRY_CHARACTER` |
| `state` | `int` | The actor's initial `AKGL_ACTOR_STATE_*` bitmask, **as a number** |
**`state` is an integer here, and only here.** Character JSON accepts the string-array form
— `["AKGL_ACTOR_STATE_ALIVE", "AKGL_ACTOR_STATE_FACE_DOWN"]` — because
`AKGL_ACTOR_STATE_STRING_NAMES` exists to decode it. `akgl_tilemap_load_layer_object_actor`
calls `akgl_get_json_properties_integer`, which demands a property Tiled declared as
`"int"`, so a map that spells the state as names raises `AKERR_TYPE`. Add up the bit values
from [Chapter 12](12-actors.md) and write the sum.
Two consequences of the registry being the identity:
- **The characters have to be loaded before the map.** An unregistered `character` name
comes back as `AKERR_KEY` from `akgl_actor_set_character`, in the middle of a partly
loaded map.
- **Two objects naming one actor do not make two actors.** The second placement takes
another reference on the first and overwrites its position, so the actor ends up wherever
the last object put it. That is the mechanism for placing one actor from two layers; it
is not a way to spawn a crowd.
An `actor` object with an empty `name` is refused with `AKERR_KEY` — an actor has to be
addressable.
### 2. A `physics.model` property gives the map its own physics
A custom property named `physics.model` on the **map root** hands the map's name to
`akgl_physics_factory` and stores the resulting backend in `map->physics`, then sets
`map->use_own_physics`. Six more properties configure it, each defaulting to `0.0` when
absent:
| Property | Tiled type | Sets |
|---|---|---|
| `physics.model` | `string` | Which backend — `null` or `arcade`. See [Chapter 14](14-physics.md) |