Why Your Tiles Seam, and What Autotiling Actually Does
Almost every seam you see is not in the art file. Symptom to cause to fix, cheapest test first, then what autotiling does and does not solve.
The level is built. The tiles snap, the collision is right, and a one-pixel line runs along every tile boundary, turning your floor into graph paper. Sometimes it is there and sometimes not: it shows while the player walks and vanishes when they stop.
You open the atlas, zoom to 800 percent, and the tiles look fine. They are fine. Almost every seam a developer sees is not in the art file. It is a filtering mode, a fractional camera position, a neighbor bleeding across a tile edge in the atlas, a mipmap level, or a grid mismatch in the importer.
A tile on screen is a quad textured from a rectangle of an atlas. Which texels the sampler reads, how they blend, and where the quad's edges land on the pixel grid decide the color at every boundary. A seam is a defect in one of those three; the drawing is a distant fourth.
Telling an artist to redraw tiles when the cause is bilinear filtering costs a day and fixes nothing. Debug it instead: symptom, cause, test, fix, cheapest test first.
The short version. Six causes cover nearly every tileset seam: bilinear filtering sampling the neighboring tile, a camera or sprite at a fractional pixel, a non-integer scale factor, mipmaps on a tile atlas, a transparent gutter where an extruded one was needed, and an import whose tile size or spacing disagrees with the file. Only the last is art. Autotiling is separate: it looks up a tile index from a cell's neighborhood, removing placement labor without reducing the art you need.
One screenshot splits the problem in half
Before opening anything, park the camera on whole numbers, set render scale to exactly 1, and capture a frame. Seam gone means motion or scale. Seam identical everywhere means the texture or the import. Seam on certain pairs only, and now you may suspect the art.
Then work the table, ordered by how often each cause is the real fault.
| Symptom | Likely cause | Test | Fix |
|---|---|---|---|
| Line on every boundary, colored like another tile | Bilinear filtering reads across the tile edge | Switch filtering to nearest | Nearest for pixel art, extrusion for painted |
| Seam comes and goes as the player moves | Camera or sprite at a fractional pixel | Freeze the camera on whole numbers | Snap the camera to whole pixels |
| Uneven intervals, some lines thicker | Scale factor is not an integer | Set scale to exactly 1, then 2 | Integer scale, or an upscaled render target |
| Only when zoomed out, worse further out | Mipmaps averaging neighboring tiles | Disable mipmaps | Mipmaps off for tile atlases |
| Thin dark or translucent line on every edge | Transparent gutter blending toward alpha zero | Eyedropper the gutter | Extrude the edge pixel outward |
| Sliver of the wrong tile on one side of each tile | Import tile size, margin or spacing wrong | Count pixels, compare import fields | Correct the import numbers |
| Specific pairs only, stable at any scale | Real mismatch at the art boundary | Place the pair alone, nearest, scale 1 | Redraw one edge |
| Level scrambles once autotiling is on | Atlas order differs from the bitmask template | Compare atlas order to the template | Re-order the atlas |
Filtering is the first suspect, and usually one dropdown
Bilinear filtering blends the four texels nearest the sample point. At the outermost pixel of a tile, some of those four belong to whatever sits beside it in the atlas, not beside it in the level. A grass tile packed next to a lava tile picks up orange along one edge, in every level, forever.
Two correct answers, chosen by art style. For pixel art, set filtering to nearest: it reads one texel, so there is nothing to blend. Painted art at arbitrary scale needs the blending, so fix the texture by extruding instead. Forcing nearest there trades the seam for aliasing on every internal edge.
Mipmaps are the same failure further down the chain. A mip level is the atlas halved, repeatedly: two levels down, a 32 px tile is 8 px and its gutter has been averaged into it. The tell is a seam that appears only when you zoom out and grows further out. Turn mipmaps off on tile atlases, since keeping them means doubling the gutter per level retained.
Both live in the importer, and a re-import restores defaults silently. Field names per engine are in the per-engine import piece.
Padding and extrusion are different fixes for different faults
Both fixes put a gutter between tiles, and what fills that gutter decides the outcome.
Transparent padding separates. The sampler can no longer reach a neighbor's color, so the orange line is gone. But it now reaches transparency, and blending a solid edge pixel against alpha zero gives a partly transparent one. You traded a colored seam for a translucent one.
Extrusion continues. Copy the tile's outermost row and column outward into the gutter, so the pixel past the edge matches the pixel at the edge. The blend becomes a no-op, because what the sampler reaches for has the color and alpha it already had. No seam, no halo.
The rule follows from what the cell does on screen. If the cell's edge touches another cell in the game, extrude. If the cell's edge is the end of the object, pad with transparency. Tiles exist to sit against each other, so a tileset is always the first case. An animation frame is the second, since two frames of a walk never appear side by side, and that is what the padding field in the sheet spec means. Reading it as permission to pad a tileset with transparency is the mistake this section exists to stop.
One pixel of gutter covers native size or an integer multiple, two if you ever draw the atlas smaller. Then tell the importer: a 32 px tile with a 2 px gutter is a 34 px stride, and an importer told 32 with zero spacing cuts every tile wrong. Margin is a separate field from spacing.
Extrusion is an export step, not a drawing one, so ask for it alongside tile size, as we do when baking a collection.
No gutter
The sampler reaches past the tile edge and picks up whatever was packed beside it in the atlas. A grass tile packed next to a lava tile carries orange along one edge, in every level, forever.
Transparent padding
The neighbor is out of reach, so the colored line is gone. The sampler now blends a solid edge pixel against alpha zero and gets a partly transparent one back. One seam traded for another.
Extruded edge
Copy the tile's outermost row and column outward into the gutter. What the sampler reaches for already has the color and alpha it had, so the pixel past the edge matches the pixel at the edge.
The integer position rule, and the seam that flickers
A camera at x = 100.37 puts every tile quad on a fractional boundary. The rasterizer decides independently, per quad, which physical pixel each edge lands on. Two tiles sharing a mathematical edge can round in opposite directions, and one column of screen pixels then belongs to neither. The background shows through.
Next frame the camera is at 100.41, the rounding changes, and the gap moves or disappears. That makes this the hardest of the six to diagnose: never reproducible from a screenshot, never twice in the same place, and absent from the editor.
Snap the camera to whole pixels and keep the render scale an integer. Compute motion in floats, round the position used for rendering, and round in the space the tiles are drawn in. Rounding world coordinates while rendering at 3x still leaves a third of a pixel.
Non-integer scale is the same defect with a fixed pattern. At 1.5x a 32 px tile occupies 48 screen pixels and every second boundary lands on a half pixel, so the seams sit at a regular interval and hold still while the camera does. For arbitrary zoom, render to a texture at a fixed integer scale and scale that texture once.
Characters standing on those tiles show the same fault as a shimmer. The loop diagnosis piece covers it from the character side.
How to find your cause, cheapest test first
In order. Stop when the seam changes behavior, because that is your cause.
- Freeze. Camera on whole numbers, scale exactly 1. Gone means motion or scale.
- Read the seam's color. Eyedropper it in a screenshot: another tile's color means bleed, the clear color a gap between quads, dark or translucent a transparent gutter.
- Set filtering to nearest. Gone means filtering. Choose between nearest permanently and extruding.
- Disable mipmaps. Only if the seam varied with zoom.
- Check the numbers. Count one tile and one gutter in the file against the import tile size, margin and spacing.
- Isolate the pair. Two tiles alone, nearest, scale 1, camera parked. Still wrong means art, and now you can say so with evidence.
Record the settled values beside the grid in your art bible. Import settings are the first thing lost when a file is re-exported.
# Seam check - [tileset name]
## Freeze test
[ ] Camera at whole pixels Y / N
[ ] Render scale integer Y / N
[ ] Seam survives freeze? Y / N
N -> motion or scale. Stop.
## Texture and import
[ ] Filtering : nearest / bilinear
[ ] Mipmaps : OFF / on
[ ] Tile in file : ___ px
[ ] Gutter : ___ px
[ ] Gutter is : extruded / transparent
[ ] Import tile : ___ (match file)
[ ] Import space : ___ (match gutter)
[ ] Import margin: ___
## Seam color (eyedropper)
another tile -> extrude
clear color -> snap camera
translucent -> gutter is empty
## Art
[ ] Pair isolated, nearest, scale 1
[ ] Still wrong -> redraw one edge
## Autotiling
[ ] Scheme: 16 / 47 / quarter-tile
[ ] Atlas order matches template Y / N
[ ] Test map: rect, L, plus, island, hole
Autotiling is a lookup from a neighborhood to a tile index
Strip the tooling away and autotiling is one function. Your map says a cell contains grass. The renderer needs to know which grass picture, so it reads the neighbors, turns their states into a number, and indexes the tileset with it. Nothing is generated; a value is looked up.
Schemes differ only in how many neighbors feed the number, and that decides the tiles you owe.
- Four neighbors, 16 tiles. North, east, south and west, each the same material or not: four bits, sixteen values, sixteen tiles. That gives fill, four edges, four outer corners, the ends of a one-wide strip and the isolated cell. It cannot express an inner corner, because an inner corner is defined by a diagonal and this scheme never reads one.
- Eight neighbors, 47 tiles. Add diagonals and the naive count is 256. Most are unreachable, since a diagonal only matters when both of its adjoining sides are filled. Collapse the rest and 256 becomes 47 tiles, with inner corners correct everywhere.
- Quarter-tile schemes, 16 pieces at half size. Pick each of a tile's four quarters from the 2 x 2 block of terrain corners around it. Sixteen quarter-pieces is four tiles of drawing surface for the corner behavior of the 47-tile set, moving cost into authoring, since every quarter must meet every other.
One failure looks like broken art and is not. Every scheme assigns bit values to directions in a fixed order, so your tiles must sit in the atlas in the order the template expects. Get it wrong and the level scrambles consistently. Re-order the atlas, not the art.
What autotiling does not solve
Say it plainly, since expectations run the other way. Autotiling does not reduce the art you need. It removes the placement labor. A 47-tile set is 47 drawn tiles whether a bitmask places them or you do. "Auto" reads as "generates," and nothing is generated.
- Seams. A tile with a bad north edge now gets placed automatically wherever that edge shows.
- Transitions. A bitmask resolves one material against "not this material." Grass meeting dirt with a specific border still needs that border authored, per pair.
- Variety. One index per neighborhood means every interior cell gets the identical fill, so large areas read as plaid. Variant selection is a separate random pick on top.
- Incomplete sets. Hand placement lets you dodge a missing inner corner by avoiding the shape. Autotiling walks into every gap in the inventory.
What you gain is real: level editing stops being pixel work. You paint regions, the edges resolve, and reshaping a room is a drag.
Terrain layering instead of a transition set per pair
Transitions are where tile counts explode, because the pairs that can touch grow as the square of the material count. Five materials that can all meet is ten transition sets on top of five base sets. Layering is the standard escape.
Rank the materials into a stack. Paint the lowest across the region, then paint the next on top as its own layer. Each material is authored once, its edge drawn against transparency rather than against a named neighbor, and whatever sits underneath shows through where this one ends. Cost becomes linear in materials instead of quadratic.
- Every boundary a material makes looks the same. Grass ending on dirt and grass ending on stone get the identical fringe. Where a join should read differently, a wet line into water against a crumble into rock, layering cannot express it.
- The order is global. Grass above dirt everywhere means you can never show dirt overlapping grass.
- You pay in cells and overdraw. Each cell holds several layers, most fully hidden, and map data grows with stack depth.
- Edges must be authored for transparency. A material with an opaque border baked in cannot be layered, so the decision precedes the art.
Mixed is normal: layer the common terrain, and author a pair set for the two or three joins the player studies.
Frequently asked questions
My tiles are fine in the editor and seam in the build. Why?
The editor camera sits still at whole coordinates and often renders at a different scale, hiding both the fractional-position seam and the non-integer scale seam. Reproduce in a build, moving. A defect in both is texture or import; a defect only in the build is position or scale.
How much extrusion do I need?
One pixel if you render at native size or an integer multiple. Two if you ever draw the atlas smaller, since a minified sample reaches further. More than two means you are compensating for mipmaps, and turning them off is cheaper.
Can I add transparent spacing instead of extruding?
It fixes the colored seam and creates a translucent one, so on a dark background you get a grid of faint outlines. Transparent spacing is correct for cells whose edges are the edge of the object, as an animation frame is. Tiles touch tiles, so their edge pixel needs continuing.
Should I use the 16-tile or the 47-tile set?
Start at 16 and build a real level. The 47-tile set buys correct inner corners, and those appear only where regions turn concave. Rectangular architecture barely produces them; caves, coastlines and cliffs produce them constantly. Upgrade when you can point at a placement that looks wrong.