Quick answer: Tile collision shapes are per-tile-ID, not per-cell. If you store rotated/flipped versions of a tile, custom collision must apply the same transform when querying.

A custom tile collision query (your own polygon-per-tile-ID lookup) returns the right shape for the base tile but the wrong one when the tile is mirrored or rotated via tile_set_mirror / tile_set_rotate.

Decode the Tile Data

var data = tilemap_get(tilemap, cx, cy);
var tid  = tile_get_index(data);
var rot  = tile_get_rotate(data);
var mir  = tile_get_mirror(data);
var flp  = tile_get_flip(data);

The packed data carries both the base tile ID and the per-cell transform. Your lookup must use the ID for the shape and then apply the transform.

Apply the Transform to the Polygon

Maintain one canonical polygon per tile ID. At query time:

  1. Apply mirror (negate X around tile center).
  2. Apply flip (negate Y).
  3. Apply rotate (90/180/270 around center).

Translate to the cell’s world position last.

Or Pre-Generate Variants

For just 8 orientations, pre-bake all 8 polygons per tile ID and pick by the combined flag value. Faster than transforming each query at the cost of memory.

Verifying

Rotate or mirror a slope tile in the room editor; collision matches the visual shape. Walking up the slope works in both orientations.

“Tile collision is per-ID; the cell adds the transform. Match both or collision drifts off the visual.”

For simple square/rectangular tiles, this never bites — but the moment you have angled slopes or one-way platforms, you need the transform-aware lookup.