Hexmap Worlds: 100 Samples From the Edge

hexmap godot zeshio terrain transitions

2. Hexmap Worlds: 100 Samples From the Edge

Post 2 in the Hexmap Worlds series — how we taught a scanner to read Zeshio transition tiles.


Post 1 was the tour: generators, fog, travel hours, the whole overland toy. This one is the thing I kept waving my hands about in that post — edge transition overlays. The difference between “a noise field with commitment issues” and “oh, that’s a coastline.”

Art is Pixel Hex / Zeshio (we’re on Zeshio_2 in the demo). Personal/stream use; I’m showing how we read the pack, not handing you the tiles. Go buy the set.

Series home: Hexmap Worlds.


The problem, in one hex

A full grass tile is easy. A transition tile is a PNG with a blob of biome art on some edges and transparency everywhere else. You’re supposed to drop it on top of a neighbor’s fill so the seam looks drawn, not stamped.

Zeshio’s classic packs even come in a tidy 30-tile wang: six rotations × arc lengths 1 through 5. One land edge, two contiguous, … five (one water edge left open). No “land on N and S with ocean in between.” Organic coastlines almost never ask for that anyway.

Coast rock transitions arranged by arc length, six rotations each

That’s the dream catalog. The catch: the filenames do not say which edges are land. And the later Zeshio_2 folders aren’t a polite 30-tile set — grass transitions alone are 114 files, with extra art variants per mask. Nobody is tagging 114 tiles by hand twice.

I wanted a tool that looked at the pixels. Walk each of the six flat-top edges. Step inward toward the hex center. Ask: is this edge actually carrying the feature, or is it just a transparent hole?

Then we built it.


100 darts, 25% inward, 50% to count

Final rule that survived the arguing:

  • Treat the image as an 84×96 flat-top hex sitting in a 96×96 PNG (Zeshio’s own layout; props are allowed to poke out).
  • For each edge — N, NE, SE, S, SW, NW — take 100 unique random samples.
  • Each sample sits on that edge and up to 25% of the way toward the center.
  • A sample “hits” if alpha ≥ 40 (so faint anti-alias junk doesn’t count as land).
  • That edge is WITH (feature present) if the opaque hit ratio is ≥ 0.5.
  • Write a lean JSON: { "file": "...", "land_edges": ["N","SE"] } and a 6-bit mask (N=0 … NW=5, same convention as Zehir’s hex layer).

Scanner samples on a grass transition — green hits, red misses

That screenshot is the whole method. Green dots landed on grass. Red dots landed on air. North is at 85% — WITH. Northeast is 44% — not enough, so we don’t lie and say the feature wraps that corner. South and southeast are a clean 0%. The JSON for this tile is basically { "land_edges": ["N"] }. Done.

Why inward, not a one-pixel rim? Because some overlays are a fat pancake and some are a mean little sliver. A rim-only probe misses the sliver or over-credits a mountain that pokes across the north edge. Sampling a band toward the center catches both personalities.

A full-ish grass overlay next to a thin sliver — both are valid transition tiles

We burned a few evenings on band width, hit ratio, “is this hex even 84×96.” The 100 / 25% / 0.5 combo is the one that matched eyeballs across the packs. Then we ran it over every Zeshio_2 * Transitions folder plus the coast overlays (those skip the word Transitions in the folder name, because of course they do).


What the game actually does with that JSON

Two layers:

  1. Base = this cell’s biome fill (random-ish full tile from that biome’s folder).
  2. Overlay = a transition tile, but only if this cell is the receiving side sitting next to a feature neighbor.

Dirt hex next to grass? Dirt keeps its fill. Grass transition overlay goes on the dirt, facing the grass. The grass hex does not also try to paint a dirt blob the other way. One owner per seam. Coast is the special case: rocky/wet overlays sit on land that faces ocean, not on the water.

Dirt fill, grass overlay, and the composited hex

That’s it. Neighbor bitmask → look up land_edges → paint. If two tiles share a mask (Zeshio_2 loves variants), we prefer the thinner overlay so we don’t smother the fill.

Zeshio_2 grass: six length-1 rotations, then length-2 pairs

Same scanner, different biome. Length-1 is “grass leaking across one edge.” Length-2 is a contiguous pair. The pack ships extra drawings per mask so a whole continent doesn’t look like copy-paste sushi.

In SimpleHexMap this is the Zeshio_2 art bridge talking to the scanned atlases. Sea level moves, biomes reshuffle, overlays re-pick. You already saw the result in post 1:

SimpleHexMap coastline using Zeshio_2 fills plus transition overlays

Those beige beaches and grassy rims aren’t a third “coast” biome painted as full hexes. They’re overlays winning the bitmask on the cells that actually touch the other thing.


What still lies to us (honestly)

  • Tall art on the north edge. Mountains poking past the hex can light up N when the “real” land is NE. We noted it on day one; the 50% threshold helps; it isn’t gone.
  • Non-contiguous masks. The 30-tile wang has no “land north and south.” Noise maps almost never need it. Canals and debug checkerboards do. Fallback is “nearest arc” or skip.
  • More underlay pairs. Dirt↔grass and land↔ocean are the showpieces. Swamp lakes, desert collars, boulder foothills — some are in, some are still a to-do on the spike list.
  • I’m not shipping the PNGs. Buy Zeshio’s pack. The scanner is the reusable bit: alpha, edges, inward, threshold.

What’s next on the map

More pairs. Coast spike still wants a little love. Rivers and roads are a different wang and I am not starting that sentence today.

If you liked the overland tour, this is the seam work that makes it feel like a place. Series page: Hexmap Worlds. Post 1: Come Look at This Map.

And yeah — I still see six red-and-green edges when I close my eyes. Occupational hazard.

— Mario