From Broken Tiles to a Full Terrain System: The Autotile Odyssey
By Claude (Anthropic) & Faye
Chapter 1: Testing Begins with a Bang
It started with a simple task: “Let’s test if the autotiles actually work.”
We’re using bluecarrot16’s LPC terrain pack — an open-source pixel art terrain tileset. Our database tracked 132 terrain transition pairs. Testing the first few, we immediately found grass_light_on_dirt was half-baked in the tileset — only 2 out of 16 corner combinations existed in the LPC source. Three other terrains had broken diagonal transitions.
Then came the first test screenshot:
“You swapped the diagonal directions lol”
Yep. The two diagonal tiles were pasted at the wrong positions. Top-left/bottom-right and top-right/bottom-left, completely reversed. A one-line swap fix, but a memorable “first bug.”
Chapter 2: Dirt Under the Water
With diagonals fixed, another screenshot arrived: water_on_grass showed dirt-colored edges at the diagonal transitions.
Investigation revealed that terrain-v7.png’s water overlay had dirt-colored shorelines baked in — that’s how LPC designed it; water always borders dirt. But we needed water transitioning directly to grass.
The solution was pragmatic: keep the terrain-map diagonals, skip the v7 overlay, and hand-fix the PNG later in Aseprite. Sometimes the best code is no code.
Chapter 3: The Database That Wasn’t Locked
Editing test results in DB Browser, the save kept failing with a “database locked” error.
Initial diagnosis: Godot’s WAL mode was locking the file. Suggestion: close Godot first. After some back-and-forth, the real cause surfaced:
“Found the issue — the table has CHECK constraints. I wasn’t entering valid values.”
Not a lock. Just our own validation rules rejecting bad input.
Chapter 4: The Five-Tile Problem
This was the turning point of the entire journey.
Painting the actual map, a fatal issue emerged: drawing a gravel path with gravel_on_dirt, anything narrower than 5 tiles showed only dirt — no gravel at all.
Godot’s Mode 0 (blob mode) requires ALL 8 neighbors of a tile to match before it shows the foreground fill. That means you need at least a 5×5 area before the center tiles render as the painted terrain. For narrow paths, single-tile bridges, and L-shaped corners, this mode simply doesn’t work.
We tried every alternative we could think of:
- Multi-layer approach: Grass layer underneath, gravel layer on top. Result — hard rectangular borders. Pre-composited opaque tiles can’t blend across layers.
- Blob tiles in Mode 1: Switch to corner matching. Result — only 2×2+ blocks showed gravel. The blob bitmask pattern is fundamentally incompatible with corner-only matching.
Both failed. Then a reference image changed everything — a screenshot of a perfect LPC village with narrow paths, small gardens, and single-tile bridges, all with smooth transitions.
“I’m sure this can be solved programmatically”
Chapter 5: Rebirth — The Corner-Based Architecture
Back to first principles. Why did the reference image work? Because it used a corner-based approach: each tile’s four corners can independently belong to different terrains. A single tile can have grass in the top-left and gravel in the top-right — no need for all 8 neighbors to agree.
This meant completely scrapping the pair-terrain approach and rebuilding as base-terrain architecture:
- 9 base terrains: dirt_tan, grass, grass_dark, grass_light, sand, soil, water, gravel, dirt_brown
- Each terrain gets one atlas containing ALL corner combinations from its interactions
- Godot Mode 1: Only matches 4 corners, works at 3×3 minimum
We rewrote the bulk of the extraction script, collecting 16 corner combinations per terrain pair and setting mixed peering bits.
The test result:
“This version actually works!”
A 1-tile-wide gravel path on grass, with perfect transitions. The breakthrough moment.
Chapter 6: Two Systems, Side by Side
“Is the old tileset still available? It’s nice for simple maps where you want full control.”
Good question. Blob mode isn’t great for narrow paths, but its 47-tile pattern provides more precise edge control for large areas. We kept both:
| Corner Mode | Blob Mode | |
|---|---|---|
| File | outside_tileset.tres |
outside_tileset_blob.tres |
| Terrains | 9 base terrains | 12 pair terrains |
| Best for | General mapping, narrow paths | Large areas, precise control |
| Min. area | 3×3 | 5×5 |
Chapter 7: The Color Palette
Shapes were right, but colors were wrong — LPC’s saturated greens and grayish browns clashed with our key art’s olive-green + warm-beige aesthetic inspired by classical Chinese paintings.
We extracted 6 color groups from the key art (stone paths, grass, trees, roofs, water, soil), then built a pixel-by-pixel remap script. Each pixel gets classified by hue, saturation, and luminance (grass? dirt? water? outline?), then mapped to the target palette using luminance-preserving interpolation.
19 autotile files, all recolored in one command. Olive-green meadows, warm beige paths — finally the classical Chinese countryside feel we wanted.
Chapter 8: Save Your Tools
A day later, a crucial question:
“Where’s the remap script? We can reuse it, right?”
The script hadn’t been saved as a file. The docs said “see commit history for the script.”
Lesson learned: useful tools deserve to be permanent. We packaged it as tools/remap_palette.py with auto-backup, dry-run preview, and selective skip. Adding a new terrain now takes one command.
Chapter 9: The Vanishing Soil Texture
Added soil_on_grass as a new terrain pair. Extract, remap, done — until the editor opened:
“The soil fill has no texture, just two flat colors”
Soil originally had 8+ shades of brown with rich plowed-earth texture. But the remap script classified all brown pixels as “dirt” and mapped them with the warm-beige dirt palette — whose darkest shade was luminance 0.55. Soil’s original 0.19–0.61 luminance range got compressed into two tones. Texture gone.
The fix: a dedicated 6-stop deep brown palette for soil (#C3975F to #4B2F20), auto-detected by filename.
Then soil_on_dirt broke too (accidentally removed from the config during “fixing”). Then bright grass highlights weren’t getting recolored (luminance threshold off by 0.05)… Three rounds of fixes before all colors were finally right.
Chapter 10: The Editor Mix-Up
“I assigned outside_tileset_blob.tres to the layer, but the tilemap editor shows outside_tileset.tres”
A closer look at the editor panel revealed: it said “Matches Corners and Sides” (that’s blob mode 0), and all terrain names were the blob pair names. It WAS showing the blob tileset — the two systems just have similar-looking names.
“Oh right, I confused myself. Sorry about that.”
Probably the most relaxing “bug” of the entire journey.
Epilogue: 12 of 132, but the Pipeline Is Ready
Looking back at this journey:
- 1 diagonal mapping bug
- 1 phantom database lock
- 1 fundamental architecture problem (the five-tile curse)
- 3 failed alternative approaches
- 1 complete architecture rewrite
- 3 color palettes (grass / dirt / soil)
- And countless “lol”s and “rofl”s
Out of 132 terrain pairs in the database, we’ve completed 12. But more importantly, the entire pipeline is built:
Change 2 lines of config → Run extraction → Run color remap → Done
The remaining 120? Ready whenever we are.
