Builder's Manual · Chapter 6

Drawing the Map

The map is where hexes stop being a data model and start being geometry. This chapter is the arithmetic, and the reason you should never do it by hand.

The geometry block

Four numbers on the region turn q and r into positions on the artwork:

x = originX + hexWidthX    × (q + r / 2)
y = originY + hexRowPitchY × r

For a radius-2 field on a 4:3 canvas: originX 0.5, originY 0.5, hexWidthX 0.16238, hexRowPitchY 0.1875.

These four numbers are the single source of truth. The region schema's _tokenX and _tokenY expressions read them, and the map generator draws the polygons from them. That is why the two can never disagree — and why editing one by hand moves every token on the board.

Two things that are easy to get wrong

The row pitch is 1.5 × the hex radius, not 2 ×. Pointy-top rows interlock; each row sits up into the gaps of the one above. Using 2R spreads the board out and leaves gaps between rows.

The x radius is divided by the canvas aspect: w = √3/A × R. Normalized coordinates run 0–1 on both axes, but on a 4:3 canvas one unit of x is wider on screen than one unit of y. Skip the correction and your hexagons render as squashed ovals. This is the single most common way a hex map comes out wrong, and it is invisible in the numbers — you only see it on screen.

For radius 2 on 4:3 this lands on exact values: R = 0.125, width 0.16238, pitch 0.1875. Nineteen tiles filling the canvas height exactly.

Painting terrain into a hexagon

Each area carries a fillImage whose picture is a square, and the hexagon clips it.

A pointy-top hex is taller than it is wide — 2R tall against √3R wide — so the square is sized to the height and overhangs left and right, and the overhang is exactly what gets thrown away. That gives scale = 2R / A, or 0.1875 on this board, with the offset placing the square's top-left corner at the hex's bounding box.

Three consequences for the art, all of them enforced in the terrain type's stored image style:

  • Terrain fills the square edge to edge including the corners, because the corners are cut off. No margins, no vignette, no frame.
  • Never draw a hexagon in the art. The map draws the outline; art that contains one produces a hex inside a hex.
  • No central focal object. One picture is reused across many hexes at once, so it has to read as a patch of ground rather than a portrait of one tree. Spread the interest evenly.

That last constraint is what made the hex conversion cheap. The dungeon needed a separate tile for every combination of doorways — twenty-one variants per room type once you go from four sides to six. Terrain needs one picture per terrain, because nothing has to line up across an edge.

The tools

node region-map.js "Regions/The Kettle.region.json" > map.json
craft map set "Regions/The Kettle.region.json" --file map.json

region-map.js reads the region, computes the polygons from its own geometry, and attaches each tile's terrain art. Terrain without art falls back to a flat colour, so a half-drawn board still reads.

Note that craft map set wants forward slashes in the workspace path, even on Windows.

Then prove it

craft map get "Regions/The Kettle.region.json" > map.json
node verify-tokens.js "Regions/The Kettle.region.json" map.json

This recomputes the token expressions exactly as the schema does and tests point-in-polygon against the stored hexes, including the creature-token offset.

Run it after any change to geometry, to the token expressions, or to the map. Drift between the schema and the artwork puts every token in the wrong place, and it is completely invisible until somebody plays.

Manual updated Aug 20, 2026.