Your wall is taller than one tile. The player walks below it and is drawn behind it. You tick Y Sort Enabled, you drag Texture Origin until the art lines up — the two steps every answer gives — and the pixels do not move at either step. They do not move because neither of those is the thing that decides the order. Below is the field that is, the formula it feeds, and a scanner that reads your scene and names which of the causes is yours. Every number here was measured on Godot 4.7-stable by rendering the scene and reading the contested pixel back, because draw order is not a property you can print.
Two 16×16 tiles in one y-sorted TileMapLayer — the scene all 28 claims are measured in. RED sits in cell (0,0), BLUE in cell (0,1). Drag RED's Texture Origin as far as you like: the picture moves and the winner of the contested band never changes. Then move Y Sort Origin by one unit at the right place and it flips.
The tie is real and it is why +16 looks broken and +17 works: at equal keys the tile in the later cell keeps the band (Y16, Y17). z_index is checked before any of it (Y26).
Nothing leaves your browser — this page has no network code in it. The scanner is the same file the terminal runs, byte for byte.
Setting texture_origin.y = -8 moves the tile 8 px down into the next cell (Y11). Setting it to +8 moves it up — the offset is subtracted, not added (Y10), which is already the opposite of what a field called "origin" suggests. Drag it as far as you like: at -64 the art is four rows from home and BLUE is still on top (Y14). The picture moved. The sort key never did.
With y-sort off, the tile written to the later cell wins the contested band (Y12). Turn y_sort_enabled on and the pixel is identical — same winner (Y13).
That is the whole trap. The advice is "enable Y Sort and set your tile origins", and someone who does exactly that, in that order, sees no change at either step and concludes y-sorting is broken. It is not broken: y_sort_enabled is the precondition, and texture_origin is the wrong field.
What you want is Y Sort Origin (TileData.y_sort_origin), which sits directly under Texture Origin in the TileSet editor and ships at 0 (Y3). Push RED's y_sort_origin to +16 and BLUE still wins (Y15). Push it to +17 and the order flips (Y16). That one-unit step is the whole formula:
RED sits in cell (0,0) of a 16 px TileSet, so its key is 0 + 8 + y_sort_origin. BLUE in cell (0,1) has 16 + 8 = 24. At +16 RED ties at 24 and loses the tie to the later cell; at +17 it reaches 25 and wins (Y17).
The same number falls out of a different scene. Park a sprite as a child of the layer and slide only its position: it goes behind the tile at y = 23 (Y19) and in front at y = 24 (Y20) — the centre of cell (0,1) again, arrived at from the other side. Which is why "set the origin to your tile's feet" is right in spirit and useless as an instruction: the number is measured from the centre of the cell, not from the top of the art, and it is negative for a tile whose base sits above centre.
With y-sort off, that same child sprite draws on top wherever it stands (Y18) — the classic "my player is always in front of everything". And TileMapLayer has its own y_sort_origin, which Node2D does not (Y6): setting it to +16 shifts every tile in the layer at once and takes the band back from the sprite (Y21).
The common report is "my walls are on one layer, my floor on another, and they never sort against each other". The common answer is that Godot cannot sort between layers and you must manage z_index by hand. Measured, that answer is wrong. Two sibling TileMapLayers, one holding tiles above and below the other's:
| Configuration | What the pixel came back as |
|---|---|
| nothing y-sorted | the later layer covers the earlier one wholesale (Y22) |
| y-sort on the layers only | identical pixels — no change (Y23) |
| y-sort on the parent only | identical pixels — no change (Y24) |
| y-sort on the parent AND the layers | the two layers interleave per tile: the upper tile goes behind, the lower one in front (Y25) |
So it does sort between layers, per tile, and it needs y_sort_enabled ticked in two places — on each layer and on the node they hang from. Tick one and you see nothing, which is exactly why people conclude it cannot be done.
TileData.z_index (Y5, ships at 0). Y-sorting only orders items that share a z_index, so a single tile left at z_index = 1 wins the band that sorting had given away (Y26) — and it works from the other side too: dropping the winner to -1 hands the band over just the same (Y27). One tile edited months ago, invisible unless you click that exact tile, and the sorting you set up is simply not consulted for it.
One knob that is not the problem, though it gets recommended: rendering_quadrant_size. Measured at 1, 16 and 128 on an otherwise identical y-sorted scene, the winner never moves (Y28). Leave it at 16 (Y7).
| What you see | Which cause |
|---|---|
| You dragged Texture Origin and the order did not budge | wrong field — Y14, fix with y_sort_origin (Y16) |
| You ticked Y Sort Enabled and nothing at all happened | precondition, not fix — Y12, Y13 |
| Walls and floor are separate layers and never interleave | the parent is not y-sorted — Y23, Y25 |
| Your player draws in front of everything, always | layer not y-sorted — Y18 |
| One tile ignores an arrangement that works everywhere else | a per-tile z_index — Y26, Y27 |
| The origin is right for your 16 px tiles and wrong for the 32 px ones | the key is the cell centre, not the art — Y17 |
| Every property looks right and the tutorial names none of them | the scene is on the superseded TileMap node — Y8 |
Draw order is not a property you can read — it is what the renderer does with the properties. So every Y id above is an assertion in verify_y_sort.gd, which builds the scene, renders it into a SubViewport and reads the contested pixel back:
docs/verify_y_sort.sh /path/to/Godot_v4.7-stable_linux.x86_64 # Y SORT: 28 passed / 0 failed
It needs xvfb-run, not --headless: the headless build swaps in a dummy renderer, SubViewport.get_texture() hands back a texture whose image is null, and there is no pixel to read. A "verification" that ran headless here would be measuring nothing and passing. The script prints the value it measured next to each claim, so if your build disagrees with this page the disagreement comes back as a number instead of an argument. The full write-up is why-y-sort-draws-the-wrong-order.md.
The scanner in this page is the same file the terminal runs, byte for byte — ysort-scan-core.js. A test refuses to ship this page if the copy served here has drifted from the copy in the repo, because otherwise that sentence would be unverifiable.
It does not flag a flat floor layer that is simply not y-sorted — that is the correct configuration for a floor, and a scanner that flags every one of them is noise. It speaks up only when something in the same file shows sorting was wanted: a tile origin pushed off its cell, or child nodes parented to the layer. Run against godotengine/godot-demo-projects — 533 scene and resource files — it reports exactly one hit, 2d/role_playing_game/grid_movement/exploration.tscn, and that one is not a bug in the demo: Player and Opponent are children of a floor layer with nothing tall on it. It is listed here as calibration. Before that gate existed, the same rules fired on 13 perfectly correct ground layers.
Being straight about the boundary, because this page is not a feature list:
y_sort_origin for you. Sorting depends on where the art sits inside each tile, which is a fact about your drawing, not about the wiring — the plugin has no way to know that a tile's visual base is 6 px above its bottom edge.TileData objects Blobsmith writes when it turns an autotile sheet into a configured TileSet — terrain set, per-tile peering bits, optional collisions. That is why the harness to measure them existed here.Thin lines between tiles — which of the four causes is yours · Terrain painting puts the wrong tile down · Why a blob autotile is 47 tiles and not 256 · The byte layout of tile_map_data · The player sees boxes instead of translated text
This scanner runs in your terminal too — it and seven others are one MIT zip with no dependencies: eight scanners for a Godot 4 project.