Nothing in the output log, no warning in the editor, and the tile looks exactly like the one two cells over that works. There are six different ways to get that symptom, the engine is silent for all six, and three of them are not in the TileSet at all — two are checkboxes on the node and one is a bit on the body, which is why the tile you keep inspecting looks fine. Below they are separated one at a time, then a scanner reads your own file. Every number here was measured on Godot 4.7-stable by building a real TileMapLayer and a real CharacterBody2D and calling move_and_collide — "does the body stop" is not a property you can print.
The scene all 26 claims are measured in: 16×16 tiles painted on row 2, so the row occupies y = 32..48. A 4×4 body starts at y = 24 and is pushed straight down. Stopped at y = 32 means it landed on the tiles; no collision means it went through them. Untick one thing at a time.
Each toggle above is one measured claim. With more than one of them off the body still falls through and the engine still says nothing; the bench names the first cause in the order verify_tile_collision.gd checks them, because that is the order that ends the search fastest.
Nothing leaves your browser — this page has no network code in it. The scanner is the same file the terminal runs, byte for byte.
For comparison, the same scene with a physics layer and a real polygon stops the body at y = 32 (C10). Not one of the six below prints anything at all.
| id | what is set | result |
|---|---|---|
| C11 | physics layer exists, that tile has no polygon | falls through |
| C12 | polygon count set to 1, points never drawn | falls through |
| C13 | TileMapLayer.collision_enabled = false | falls through |
| C14 | TileMapLayer.enabled = false | falls through (the whole layer is off) |
| C15 | physics layer collision_layer = 0 | falls through |
| C16 | tiles on bit 2, body's mask on bit 1 | falls through |
Three of the six are not in the TileSet: C13 and C14 are checkboxes on the node, and C16 is a bit on the body. That is the whole reason the tile "looks fine" — you are inspecting the wrong object.
A TileSet physics layer has two bit fields side by side, collision_layer and collision_mask. Only the first one gates this. Set the physics layer's collision_mask to 0 and the body still stops (C17).
That column is what the tiles detect, not permission for a body to stand on them. If you are hunting a fall-through by flipping bits in it, you are flipping bits that cannot produce the symptom — which is why the bench above lets you untick it and watch nothing happen.
A TileSet starts with zero physics layers (C1). Ask a tile about a polygon on layer 0 before adding one and the engine pushes an error into the log — and get_collision_polygons_count(0) still returns 0 (C2). Code that checks the count instead of reading the log sees a perfectly ordinary zero.
Add the layer and it defaults to collision_layer = 1 (C3) and collision_mask = 1 (C4) — which is why "it just works" for most people, and why C15 and C16 look like someone else's problem right up until you inherit a project where those bits were edited.
A collision polygon needs 0 points or at least 3. Hand it 2 and the engine refuses the assignment:
Both print an error and neither changes what you see in the picker. And a polygon whose count is 1 with nothing drawn in it — the state you get by clicking add polygon and then moving on — is saved to disk byte-identically to a tile with no polygon at all (C12). On the file, and to the engine, they are the same nothing, which is why the scanner above reports them the same way.
create_alternative_tile() returns a tile with no collision shape from its base (C6). If you use alternatives for variants — and an autotile terrain often does — every one of them needs its own polygon.
This is not a corner case. In Godot's own 2d/platformer demo the tiles that collide carry the polygon rewritten on all eight flip/transpose alternatives, with the coordinates mirrored by hand:
0:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-32, -22, 32, -22, 32, 32, -32, 32) 0:0/1/flip_h = true 0:0/1/physics_layer_0/polygon_0/points = PackedVector2Array(32, -22, -32, -22, -32, 32, 32, 32)
That repetition is what "inherits nothing" costs in a real project — and it is the case the scanner reports separately, because a missing alternative reads as a tile that works most of the time.
Paint an asymmetric polygon — the left half of the tile only — and the body lands on the left half (C18) and falls through the right half (C19). Paint the same cell with TRANSFORM_FLIP_H and the solid half moves to the right (C20). That is correct, and it means a mirrored cell cannot be debugged from the atlas alone: the shape you are inspecting is not the shape in that cell.
32 px art on a 16 px grid with the polygon drawn to the art: collision reaches 8 px above the cell — the body stops at y = 24, not y = 32 (C21). Invisible ledges floating above tall tiles are this, not a bug in your level.
One-way is per polygon, and one_way_margin defaults to 1.0 (C8).
So a one-way platform a fast-falling player tunnels through is not a physics bug: C24 and C25 isolate one-way as the difference — same polygon, same 48 px step, one flag apart. The knob the engine offers for it is one_way_margin; this page measured its default and not its cure.
Visible Collision Shapes is a debug draw (C9). It renders the shapes that are there, never the ones that are missing, and it shows nothing headless and nothing in CI. There is no view in the editor whose job is to answer "which of my painted tiles has no shape" — which is the one question the scanner on this page exists to answer, and the only one of the six it can answer from the file.
C13 through C17 live in node state and in layer bits, not in the tileset file. A green result means every painted tile carries a shape. It does not mean collision works — the bench at the top of this page is the rest of the list, and it is the rest on purpose.
It also cannot separate C12 from C11, because the engine does not: a polygon with a count and no points is written to disk exactly as a tile with no polygon. Both come back as no-collision-shape.
The scanner above is docs/find_tile_collision_gaps.js and it runs over a whole project at once, with no dependencies, no engine and no project import:
node docs/find_tile_collision_gaps.js /path/to/project node docs/find_tile_collision_gaps.js --json project/
Exit code 1 when it found something and 0 when every painted tile carries a shape, so it works as a gate. Measured against godotengine/godot-demo-projects at 34fc995: 13 files with a TileSet, 703 painted tiles, 75 findings — five of them one-per-tileset in demos that move on a grid and never ask physics about a tile, and the rest decoration and background props in the two platformer demos. Read that as a list to review, not as 75 bugs in the official demos: the scanner lists what a body will pass through, it does not know what you meant.
It ships with eight others in one MIT zip, no email and no account: the nine Godot 4 scanners. The rules it applies are docs/collision-scan-core.js — the same file this page loads.
TileSet have a physics layer at all (C1)?collision_enabled, enabled (C13, C14).collision_layer vs the body's collision_mask (C15, C16) — and not the layer's collision_mask column (C17).The free Blobsmith Autotile Wirer addon writes one full-square collision polygon per tile on physics layer 0 as it wires the sheet, so C11 does not happen to a set it generated. It does not draw per-shape edge collision, it does not touch the node checkboxes of C13 and C14, and it does not choose your layer bits. Same job in the browser, nothing to install: drop a sheet and get the wired .tres back.
The free 47-blob starter pack is 8 already-wired TileSets, MIT, no account — each one painted and read back in a real engine. Useful here for one specific reason: it is a set you know is wired right, so a fall-through in your project stops being ambiguous.
Terrain painting put the wrong tile in the middle of a field · thin lines between tiles that touch in the PNG · Y Sort is on and the tiles still draw in the wrong order · why a blob autotile is 47 tiles and not 256
This scanner runs in your terminal too — it and eight others are one MIT zip with no dependencies: nine scanners for a Godot 4 project.