Your tiles touch in the PNG.
The game draws a line between them.

A one-pixel seam along the tile borders, worse while the camera moves, gone when it stops. There are four different causes with four different fixes, every answer online tells you to do all four at once, and one of the four is a setting that does not exist in Godot 4. Paste your project.godot below and this runs the seam-cause scanner from the Blobsmith Autotile Wirer repo, in your browser, and tells you which one is yours. Everything under the tool is measured against Godot 4.7-stable, claim by claim.

The four project-wide causes live here.

The scene with your TileMapLayer, or your TileSet resource.

Nothing is uploaded — the scan runs in this page, and it only reads. A ▲ cause is something to change; a · note is something to know. On a brand-new project the scanner correctly reports causes before you have drawn a single tile.

    First, the fix that no longer exists

    The most repeated answer to this question is "Project Settings → Rendering → Quality → 2D → Enable Pixel Snap". That setting is rendering/quality/2d/use_pixel_snap, and in Godot 4 there is no such property (S1). Neither is there use_gpu_pixel_snap (S2). Those are Godot 3 names.

    If you went hunting for that checkbox, failed to find it, and concluded you were in the wrong menu: you were in the right menu, reading advice written for the previous engine.

    The Godot 4 replacement is two settings, not one (S3, S4), and both ship off (S5):

    rendering/2d/snap/snap_2d_transforms_to_pixel
    rendering/2d/snap/snap_2d_vertices_to_pixel

    Turning them on is step 3 of the fix below — not step 1, because on a stock project it is not the first thing that is wrong.

    Cause 1 — the filter, and the enum that means the opposite thing

    The default canvas-texture filter in a new Godot 4 project is not Nearest.

    rendering/textures/canvas_textures/default_texture_filter is an enum whose values are, in order, Nearest, Linear, Linear Mipmap, Nearest Mipmap (S6), and it ships as 1 — Linear (S7). So in a project where nobody touched it, the GPU blends between neighbouring texels of your atlas, and at the edge of a tile the texels it blends toward belong to the next tile over. That is the line.

    Now the part that costs an afternoon. On a node, CanvasItem.texture_filter is a different enum: 0 is Inherit, 1 is Nearest, 2 is Linear (S8). The same integer 1 means Linear in the project setting and Nearest on the node — so a value copied from a forum answer into the wrong one of those two does the opposite of what it did for the person who posted it. A fresh TileMapLayer ships at 0, Inherit (S9): it does not force Nearest for you, it takes whatever the project says.

    And TileSet has no filter property at all (S10). If you have been looking for this switch inside the TileSet editor, it is not there and never was — the filter belongs to the node and to the project, never to the tileset.

    Cause 2 — the gutter Godot 4 already builds for you

    The second most repeated fix is to re-export your atlas with 1–2 px of padding between tiles. On Godot 4 that is usually redundant work, because the engine does it at runtime.

    TileSetAtlasSource.use_texture_padding defaults to true (S11). With it on, the engine builds a padded copy of your atlas: tile (0,0), authored at (0,0), sits at (1,1) in the runtime texture (S12), and two 16 px tiles drawn 16 px apart in your file end up 18 px apart in the copy the GPU samples (S13). The tile keeps its size — the pad is added around it, the art is not shrunk into it (S14). Switch the flag off and the runtime region collapses back onto the authored one (S15): that is the toggle that removes the guard, and the scanner above flags it.

    margins and separation both default to (0,0) (S16), so a sheet drawn with no gutter at all is the expected input, not a compromise. Cutting one by hand still works — margins = 1, separation = 2 moves tile (1,0) from x=16 to x=19 (S17) — but it costs you tiles: the same 64×64 sheet holds a 4×4 grid with no gutter and only 3×3 with one (S18). That is why the scanner reports a hand-cut gutter as a note and not as a bug.

    If padding is on and you still have seams, padding is not your problem. Go back to cause 1, or on to cause 3.

    Cause 3 — the seam that only appears while the camera moves

    If the line comes and goes as you pan, sampling is fine and the geometry is landing between two screen pixels.

    display/window/stretch/scale_mode exists, its enum is exactly fractional, integer, and it ships as fractional (S19, S20), while display/window/stretch/mode ships as disabled (S20). Two consequences worth being precise about, because they are the difference between a fix and a wasted evening:

    Camera2D has no pixel-snap property (S24) — nothing on the camera rounds its own position for you; that is what the two snap_2d_* settings are for. And Camera2D.zoom is a float pair defaulting to (1,1) (S25), so a 2.5 zoom is one keystroke away and re-opens the seam that integer stretching just closed.

    Cause 4 — the art

    TileSet.uv_clipping ships off (S26). If the scanner finds nothing and the line is still there, the remaining explanation is that the tile does not occupy its whole cell in the source PNG. A transparent or off-colour row along the bottom of a cell reads exactly like a seam, and no engine setting will close it — this is the one cause that lives in the image, not in the project.

    The whole fix, in the order that matters

    1. default_texture_filterNearest (S7), and delete any texture_filter override left on a TileMapLayer (S8, S9).
    2. Leave use_texture_padding on (S11) and stop re-exporting the atlas with gutters (S16, S18).
    3. Both snap_2d_* settings on (S5).
    4. Only if you are stretching: scale_modeinteger (S20), and keep the camera zoom a whole number (S25).
    5. Still there? It is in the PNG, not in the settings (S26).

    Everything here is measured, not remembered

    Each S id above maps to an assertion in verify_tile_seams.gd, which asks a real engine instead of quoting a forum. It runs headless in about a second, twice — once on a stock project, once on the same project with the pixel-art settings written in:

    docs/verify_tile_seams.sh /path/to/Godot_v4.7-stable_linux.x86_64
    # ### pass 1 — stock project, nothing configured
    # TILE SEAMS: 23 passed / 0 failed
    # ### pass 2 — the same project with the pixel-art settings written in
    # TILE SEAMS: 26 passed / 0 failed

    Measured on 4.7-stable (official). The script prints the number it measured next to each claim, so if your build disagrees with this page, the disagreement comes back to you as a number and not as an argument. The full write-up, with all 26 claims, is why-tiles-have-seams.md.

    What you seeWhich cause
    A line on every tile border, all the time, camera stillthe filter — cause 1 (S7, S8)
    Seams only on some tiles, and you switched padding offthe atlas guard — cause 2 (S11, S15)
    Flickers while panning, clean when the camera stopsgeometry between pixels — cause 3 (S5, S20, S25)
    Scanner is clean and the line is still therethe art — cause 4 (S26)
    You found "Enable Pixel Snap" in a tutorial and not in the editorGodot 3 name, gone in 4 (S1, S2)

    The scanner in this page is the same file the terminal runs, byte for byte — seam-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.

    Which tool owns which half

    Being straight about this, since four of the five causes above are settings and none of our tools touches a setting in your project: