A Tileset for The Hollow
One square of ground, four hundred squares of valley
There is one tile of soil in assets/tiles/ and a terrain grid with three
kinds of ground in it. A viewport twenty cells across and twelve down is 240 cells, so
those three kinds get drawn a few hundred times a frame from a handful of pictures.
A tileset draws the ground once and places it everywhere, which buys the memory and the drawing time and hands back one problem in exchange. The transition is drawn on the water side alone: every water tile carries the whole boundary, bank and shallows and waterline, on the sides that face land, so whatever sits beside it can be soil, rock or a material added to the sheet, and never has to know.
The problem lives at the boundaries. Draw each terrain as a single square and every place a pond meets the earth becomes a straight cut sixteen pixels long: soil on one side, water on the other, nothing in between. No bank, no shallows, no waterline.
It reads as two colours of graph paper meeting, and it reads that way loudest of all the boundaries on the map. The palette put water a full step brighter than everything else: soil's midtone sits at value 46 and water's at 60, so of every edge in The Hollow, the wet one has the most contrast across it and the eye finds it first.
Fixing it means drawing the boundary, and drawing a boundary means knowing how many different boundaries there are. A pond can meet the land on its north side, or its west, or on three sides at once if it narrows into an inlet. Each of those is a different picture.
The count is the first thing to settle. assets/tiles/ holds one sheet of
twenty tiles: sixteen water transitions addressed by a number computed from four
neighbours, plus two soils, a rock, and a second open water. The checker from the last
chapter grows two rules, one of which catches an error that eight of the sixteen tiles hide
by being accidentally right.
Sixteen ways a pond can end
Take one water cell and ask four questions. Is the cell north of it water, or land? Two answers. East: two answers. South, west: two each. Four questions with two answers apiece is 2 × 2 × 2 × 2, which is sixteen combinations, and sixteen is the size of the drawing job.
Sixteen combinations need sixteen names, and the cheapest names are numbers. Give north the number 1, east 2, south 4, west 8, then add up the directions where the neighbour is land. A cell with land above it and land to its left scores 1 + 8 = 9. A single wet cell in the middle of a field scores 1 + 2 + 4 + 8 = 15. Open water, land nowhere near it, scores 0. The doubling is what makes this work: no two different sets of 1, 2, 4 and 8 add to the same total, so one small number carries all four answers and can be handed straight to an array as an index.
Figure 18.1: the four questions, their numbers, and where the answer lands. Because the sheet is drawn in mask order, the number the neighbours produce is the cell number, and no lookup table stands between them.
Sixteen is a count of combinations, and it collapses under rotation. Sort the masks by how many sides are land: no sides is one mask; one side is four, since the single bank can be north, east, south or west; two sides splits into two opposite banks, which is two masks, and two adjacent banks, which is four; three sides is four; four sides is one. That is 1 + 4 + 2 + 4 + 4 + 1 = 16, sorted into six families whose members are the same picture turned a quarter turn at a time.
So the drawing job is six tiles and a rotation, and this book draws sixteen anyway. Two reasons, both concrete. Turning a tile turns its light with it: the last chapter fixed one sun in the upper left, and a north bank rotated 180 degrees puts its shadow on the south side of the water where the light should be. And a set drawn in mask order needs no rotation code at all, since the mask is already the index. Ten extra tiles is 2,560 pixels and an afternoon.
Asking the corners as well raises the price, and the price has to be counted before the design declines it. Eight neighbours would be 28 = 256 combinations, but most are the same picture: a corner only changes anything when the two sides beside it are both open water, because otherwise a bank already covers it. Count the free corners in each family. Open water has four, so it becomes 24 = 16 pictures. A single bank leaves two free corners, four pictures each. Two adjacent banks leave one, two pictures each. Everything else leaves none. 16 + 16 + 2 + 8 + 4 + 1 = 47 distinct tiles, three times the art for the diagonal cases alone.
Four bits cannot see diagonally, so one arrangement comes out slightly wrong: a corner of land touching a water cell at the diagonal only. All four of that cell's neighbours are water, so it draws as open water, and the bank coming along the land stops at its own tile edge and leaves the corner unbanked. Figure 18.4 has one. The fix is the 47-tile set above, and this valley does not have a coastline that earns 47 tiles.
Two soils, a rock, and the weight they share
In LibreSprite (libresprite.github.io), View > Grid > Grid Settings set to 16 by 16 draws the cell boundaries on the canvas, and View > Tiled Mode > Both Axes repeats the canvas in every direction while you draw, so a tile's edges are visible against their own neighbours as you place pixels. Pixelorama has both under the same menu with slightly different names. Everything below assumes those two are on.
Ground first, since the water set has to meet it. The soil tile from the last chapter is
256 pixels: 219 of plain soil-m, and 37 marks in two-pixel and three-pixel
runs, 24 of them soil-d and 13 soil-l. Repeated across 240
cells, one tile shows its own pattern; the eye is far better at finding a repeat than at
reading a texture, and after a few seconds of looking at a field of one tile you stop
seeing earth and start seeing wallpaper.
legend s soil-m b soil-d t soil-l r rock-m k rock-d h rock-l
w water-m d water-d l water-l
soil A (chapter 17) soil B rock
sssssssssssbbsss ssssbbssssssssss rrrrrrhhrrrrrrrr
ssbbssssssssssss ssssssssssttssss rrrrrrkkkrrrrrrr
sssssssssttsssss sssssssssssssbbb rrrrrrrrrrrrrrrr
ssssbbsssssssbbs ssssssssssssssss rrrrrrrrrrrhhrrr
ssssssssssssssss sbbbssssttssssss rrrrrrrrrrrkkkrr
sbbbssssssttssss ssssssssssssssss rrhhrrrrrrrrrrrr
sssssssttsssssss sssssbbsssssssss rrkkkrrrrrrrrrrr
ssssssssssssbbss ssttssssssssssss rrrrrrrrrrrrrrrr
sssssssssssbbsss ssssssssssssssss rrrrrrrrhhrrrrrr
ssttssssssssssss sssssssbbbssstts rrrrrrrrkkkrrrrr
sssssssbbsssssss bbssssssssssssss rrrrrrrrrrrrrkkr
sssssbbsssssssss ssssssssssbbbsss kkrrrrrrrrrrrrrr
sssssssssssssstt ssssttssssssssss rrrrrhhrrrrrrrrr
sbbsssssssssssss ssssssssbbssssss rrrrrkkkrrrrrrrr
sssbbbssssssssss sttsssssssssbbbs rrrrrrrrrrkkrrrr
ssssssssstttssss ssssssttssssssss rrrrrrrrrrrrrrrr
Soil B holds 37 marks too: 23 dark and 14 light, against A's 24 and 13. Those two numbers are the whole discipline. A variant has to differ everywhere and match on average, so that a field alternating between them has no pattern in it and no patch that is heavier than its neighbour. Not one mark of B sits where a mark of A sits, and both tiles carry the same amount of ink.
Rock is the third column and a different kind of surface. Its marks come in pairs: two
pixels of rock-l with three of rock-d directly beneath them,
reaching one pixel further right. Five pairs, plus three loose dark runs. Each pair is a
small ledge lit from the upper left with its own shadow stepping down to the lower
right, the same sun the walker was shaded under, and it is the reason a field of this
tile reads as broken stone instead of grey soil: every mark on it leans the same way.
Now the failure that only exists at field scale. Draw a variant with something in it, a patch of dried crust or a stone or a tuft, and alone on the canvas it is the better tile of the two. Place it every other cell and the thing you drew turns into a lattice: the eye finds it once, finds its copy sixteen pixels away, and from then on sees a grid.
Figure 18.2: assets/tiles/tiling-compare.png: the same
ten-by-four field, alternating soil A with two different second tiles. Top: a variant
carrying one distinctive light patch. Bottom: the shipped soil B. Nothing was placed
differently between the panels; only the second tile changed.
Checkerboarding is the same failure with the tonal weights instead of a feature. A second tile that is a little brighter or a little busier than the first turns an alternating field into a chessboard, since every other cell is now a slightly different colour of ground. Both faults are invisible while you draw, which is the point of turning tiled mode on, and both are why the two soils were counted mark for mark before either was called finished.
The ring is the tile
Water is drawn quieter than soil: 17 marks against 37, five short dark ripples and two
light glints. It is the brightest material on the map and the one the eye goes to, so a
repeat announces itself there sooner than anywhere else. Every one of those marks sits
inside the middle twelve-by-twelve of the tile, which leaves a two-pixel ring around the
outside holding nothing but flat water-m. That ring is where the sixteen
tiles differ, and it is the only place they differ.
mask 0: no land mask 1: land N mask 3: land N+E mask 15: land NESW
wwwwwwwwwwwwwwww dddddddddddddddd dddddddddddddddd dddddddddddddddd
wwwwwwwwwwwwwwww wwdddwwdddwwddww wwdddwwdddwwddwl dwdddwwdddwwddwl
wwwwdddwwwwwwwww wwwwdddwwwwwwwww wwwwdddwwwwwwwll ddwwdddwwwwwwwll
wwwwwwwwwwwwwwww wwwwwwwwwwwwwwww wwwwwwwwwwwwwwll ddwwwwwwwwwwwwll
wwwwwwllwwwwwwww wwwwwwllwwwwwwww wwwwwwllwwwwwwwl ddwwwwllwwwwwwwl
wwwwwwwwwddwwwww wwwwwwwwwddwwwww wwwwwwwwwddwwwwl dwwwwwwwwddwwwwl
wwwwwwwwwwwwwwww wwwwwwwwwwwwwwww wwwwwwwwwwwwwwll dwwwwwwwwwwwwwll
wwwwwwwwwwwwwwww wwwwwwwwwwwwwwww wwwwwwwwwwwwwwll ddwwwwwwwwwwwwll
wwwdddwwwwwwwwww wwwdddwwwwwwwwww wwwdddwwwwwwwwll ddwdddwwwwwwwwll
wwwwwwwwwwwwwwww wwwwwwwwwwwwwwww wwwwwwwwwwwwwwwl ddwwwwwwwwwwwwwl
wwwwwwwwwwwwllww wwwwwwwwwwwwllww wwwwwwwwwwwwllwl dwwwwwwwwwwwllwl
wwwwwwwdddwwwwww wwwwwwwdddwwwwww wwwwwwwdddwwwwll dwwwwwwdddwwwwll
wwwwwwwwwwwwwwww wwwwwwwwwwwwwwww wwwwwwwwwwwwwwll ddwwwwwwwwwwwwll
wwwwwwwwwwddwwww wwwwwwwwwwddwwww wwwwwwwwwwddwwll ddwwwwwwwwddwwll
wwwwwwwwwwwwwwww wwwwwwwwwwwwwwww wwwwwwwwwwwwwwwl dwllwwlllwwlllwl
wwwwwwwwwwwwwwww wwwwwwwwwwwwwwww wwwwwwwwwwwwwwwl dlllllllllllllll
A bank on the north is a solid row of water-d along row 0 with a broken row
of the same colour under it, so the darkness thins out into the water instead of ending
in a stripe. West is the identical treatment turned into columns. South and east get
water-l instead, one solid line and one broken one, and the difference is
the sun: land north or west of the water stands between it and the light and throws its
shadow across the near bank, while land to the south or east leaves the shallows in
front of it lit. Rotating a tile would move that decision to the wrong side, which is the
whole argument for drawing sixteen.
Where two bands meet, in mask 3 and mask 15 above, the dark one takes the corner. A shadow falling across a lit shallow is what happens outdoors, and picking a winner once keeps every corner on the sheet consistent. Note also that each band runs the full sixteen pixels rather than stopping short. A bank drawn from column 0 to column 14 leaves column 15 open, and open water at column 15 sits against open water at the next tile's column 0, so the shoreline of a long pond breaks once every sixteen pixels along its length.
// internal/render/tileset.go — the terms the sheet is drawn to
// A water cell's four neighbours, one bit each: the bit is set when that
// neighbour is land, so mask 0 is open water and mask 15 is a single wet cell
// with a bank on every side. Cell index IS the mask.
const (
LandN = 1
LandE = 2
LandS = 4
LandW = 8
)
// TileCell is the size of one tile, TileCols how many sit across the sheet,
// so cell i lives at column i%TileCols, row i/TileCols.
const (
TileCell = 16
TileCols = 4
)
// The tiles that are not part of the transition set.
const (
CellSoilA = 16
CellSoilB = 17
CellRock = 18
CellWaterB = 19
CellCount = 20
)
Twenty tiles, four across, five down: a 64 by 80 sheet with cells 0 to 15 in mask order, the two soils and the rock after them, and a second open-water tile in the last cell, doing for ponds what soil B does for fields. A cell's column is what is left over when its index is divided by four, and its row is how many whole fours fit: the same divide and remainder that turned a flat slice index into a grid coordinate back in volume 1.
Those constants are the asset convention, and putting them in the code instead of in a
filename or a comment matters more than it looks. Nothing downstream parses
valley-tileset.png to work out what is in it; the sheet is a rectangle of
pixels plus this file, and anything that wants to read it reads both.
Figure 18.3: assets/tiles/valley-tileset.png, shown at
about seven pixels to one. Reading in mask order: cell 0 open water, cell 1 a bank to the
north, cell 2 shallows to the east, and so on to cell 15, wet on the inside and land on
every side. The bottom row is soil A, soil B, rock, and the second open water.
172 joins and 68 shore edges
Twenty tiles is twenty chances to draw something that looks right alone and wrong in place, and two of those mistakes are the kind an eye slides over. The first is a pixel of the wrong material at an edge, which shows up in the finished map as a fleck of soil in the water somewhere along a shoreline. The second is worse: a tile whose art disagrees with the mask it was drawn for, so the map places a shore facing the wrong way. Both are exact questions about specific pixels, so both belong to a program.
Rule one asks for continuity. Wherever the map can put two tiles side by side, the sixteen
pixel pairs along that join must be the same material: soil against soil, water against
water. Material, not colour, because asking for identical pixels would ban texture near an
edge and this ground is nothing but texture. The .gpl palette file has
carried the names since the last chapter, and soil-d and soil-l
are both soil by the simplest reading of the name.
Rule two asks the art whether it agrees with its own mask. A side whose bit says land must hold that side's shore colour, and a side whose bit says water must hold open water. It reads only the middle eight pixels of each edge, since the corners belong to two sides at once and one of them has to win.
// cmd/palcheck/seams.go
// A side of a tile, and the tone the shore takes there. The light comes from
// the upper left, so a bank to the north or west throws its shadow onto the
// water and a bank to the south or east leaves lit shallows.
var sides = []side{
{"N", render.LandN, "water-d"},
{"E", render.LandE, "water-l"},
{"S", render.LandS, "water-l"},
{"W", render.LandW, "water-d"},
}
// material is the ramp a palette entry belongs to: "soil-d" is soil, "ink" is
// ink. Two pixels agree at a seam when they are the same material, not when
// they are the same color.
func material(name string) string {
if i := strings.IndexByte(name, '-'); i >= 0 {
return name[:i]
}
return name
}
// touching reports whether the map can put cell a immediately west of cell b
// (horizontal) or immediately north of it (vertical).
func touching(a, b int, vertical bool) bool {
ma, wa := maskOf(a)
mb, wb := maskOf(b)
if wa && wb {
if vertical {
return ma&render.LandS == 0 && mb&render.LandN == 0
}
return ma&render.LandE == 0 && mb&render.LandW == 0
}
if !wa && !wb {
return landMaterial(a) != "" && landMaterial(a) == landMaterial(b)
}
return false // a water-land join is rule 2's business
}
// cmd/palcheck/seams.go — rule 2, once per side of every water tile
for c := range render.CellCount {
mask, water := maskOf(c)
if !water {
continue
}
var missing, extra []string
for _, sd := range sides {
edges++
want := "water-m"
if mask&sd.bit != 0 {
want = sd.tone
}
bad := false
// The corners answer to two sides, so judge the middle eight.
for i := cell / 4; i < cell-cell/4; i++ {
if name(edgePixel(s, c, sd.name, i)) != want {
bad = true
}
}
if !bad {
continue
}
if mask&sd.bit != 0 {
missing = append(missing, sd.name)
} else {
extra = append(extra, sd.name)
}
}
touching is the part that keeps the rules honest, because it derives the
legal neighbours from the masks instead of taking a list from the author. Two water
tiles can sit side by side only when the left one claims no land to its east and the
right one claims none to its west; two land tiles only when they are the same material.
A water tile against a land tile is left out of rule one on purpose, since those two
sides are supposed to differ, and rule two is what inspects that join.
The sixteen rings were drawn in mask order, one after another, from a list written down before the constants file existed: north, south, east, west, numbered 1, 2, 4, 8. The sheet looked fine. Every tile has a bank where a bank belongs, every band runs edge to edge, and the palette pass is clean. Then the seam rules ran:
$ go run ./cmd/palcheck -pal assets/palette/valley-16.gpl -cell 16 -seams assets/tiles/valley-tileset.png
assets/palette/valley-16.gpl: 16 colors loaded
assets/tiles/valley-tileset.png 64x80 5120/5120 opaque 9 colors all on palette
pixels sha256 824f85df9335927c37685bafb9745787458900a4558d181cf853038a025e67dd
20 cells of 16, 4 across
seams: 172 joins (2752 pixel pairs), 68 shore edges
cell 2 (mask 2, land E): no shore on E, the S edge is not open water
cell 3 (mask 3, land NE): no shore on E, the S edge is not open water
cell 4 (mask 4, land S): no shore on S, the E edge is not open water
cell 5 (mask 5, land NS): no shore on S, the E edge is not open water
cell 10 (mask 10, land EW): no shore on E, the S edge is not open water
cell 11 (mask 11, land NEW): no shore on E, the S edge is not open water
cell 12 (mask 12, land SW): no shore on S, the E edge is not open water
cell 13 (mask 13, land NSW): no shore on S, the E edge is not open water
seams: 8 DISAGREEMENT(S) with the sheet's own terms
Eight complaints, and every one of them is about east and south. North and west are never mentioned, in any tile, which rules out a drawing habit and points at the two numbers those directions carry. The constants go round the compass clockwise: north 1, east 2, south 4, west 8. The list the rings were drawn from went north, south, east, west, so bit 2 was drawn as a bank to the south where the constants call it east, and bit 4 as shallows to the east where the constants call it south.
Which explains why it survived a careful look. Any mask where the east bit and the south bit are equal is unaffected, since swapping two equal bits changes nothing: masks 0, 1, 6, 7, 8, 9, 14 and 15 came out correct. That is half the set right, including the two easiest to check by eye, open water and the single wet cell. The other eight have their bank and their shallows swapped onto each other's sides, and in a pond of blue tiles at 100% zoom that is a subtle wrongness rather than an obvious one: the pond looks lit from an angle that changes as you follow its edge round.
$ go run ./cmd/palcheck -pal assets/palette/valley-16.gpl -cell 16 -seams assets/tiles/valley-tileset.png
assets/palette/valley-16.gpl: 16 colors loaded
assets/tiles/valley-tileset.png 64x80 5120/5120 opaque 9 colors all on palette
pixels sha256 c4f347e030bd4d159687fa0ff4847d009644cf3160e2a5acfa02047b613cb754
20 cells of 16, 4 across
seams: 172 joins (2752 pixel pairs), 68 shore edges
seams: every join continuous, every mask drawn
1 file(s) checked, 0 finding(s)
The summary line is worded differently from chapter 17's on purpose, since a checker
that now reports seam faults as well as stray colours cannot honestly end on
0 off-palette color(s): 3 files checked, 0 off-palette color(s)
has become 1 file(s) checked, 0 finding(s), and that is the one line to
expect changed if you are diffing your terminal against the earlier chapter's.
-cell 16 is doing one job before either rule runs: it sends the file
through the sheet loader instead of the plain one, and that loader has refused a file
whose width or height does not divide into cells since the chapter that wrote it. A
sheet 64 by 78, or one exported with a stray row of canvas along the bottom, never
reaches the seam rules at all; it comes back as a loader error naming the size.
172 joins is every arrangement the map can produce out of these twenty tiles, counted in both directions: 81 pairs of water tiles that meet side by side, 81 that meet one above the other, and 10 land pairs among the soils and the rock. Each join is sixteen pixel pairs, so 2,752 comparisons of one material against another, plus 68 edges where a mask claims something about a shore and the pixels have to back it up. Nine colours out of sixteen appear on the sheet, three from each ground ramp, and every pixel is opaque: a tile with a hole in it would leave whatever the framebuffer already held showing up through the ground.
The four-bit sheet grammar
A tileset is a small vocabulary with a grammar attached. The vocabulary is twenty pictures; the grammar says which of them may stand next to which, and it is not a matter of taste. Tile 8 may sit west of tile 0, since its only bank faces away from the join, and tile 0 may not sit west of tile 8, since that would put a west-facing bank against open water: arithmetic on four bits, not an opinion about ponds. Once the grammar is written down as a function, a whole class of art defect stops being something you hope to catch in a screenshot. The checker walks every legal arrangement in a millisecond, and no amount of looking at a finished map does that, since plenty of those arrangements will not occur in any map you happen to render.
The palette did this for colour and the sheet does it for placement. Both are agreements between a person drawing and a program reading, and both were turned into files a third program can hold the first two to. That is the pattern to take away, and it is the same one that hashed a framebuffer instead of trusting a glance: when two sides of a boundary have to agree about something, write the something down somewhere a test can read it.
The set still gets three things wrong, because a tileset is never done. The diagonal case from the note above is visible on the west shore of the pond below. Rock ships with one tile where soil got two, so a wide field of stone shows its repeat. And the boundary between soil and rock is not drawn at all, which is the deliberate one: soil's midtone sits at value 46 and rock's at 44, two points apart, against the fourteen points separating soil from water. Two materials of the same weight meeting along a straight line read as a change of ground; two materials fourteen points apart read as a cut. The expensive boundary got the sixteen tiles.
Figure 18.4: assets/tiles/valley-preview.png: 240 cells
of the tileset laid out by hand in the editor, with two frames of the walker pasted on
top. Twelve of the sixteen masks appear. The notch on the pond's west side holds the
uncovered diagonal from the note above: a corner of soil meets open water with no bank
drawn between them.
That picture is 20 tiles, 16 colours and one sun, and it is the bar this book set itself in the last chapter: a world somebody would screenshot without being asked to. Nothing in it needed drawing talent. It needed a fixed palette, a fixed light, marks that travel in clusters, variants that match on average, and a program that reads the joins.
The optional quiet tileset
Passing the seam checks says the tiles fit; it says nothing about how much attention their texture takes. Compare the same authored patch of ground with two atlases below. The left panel uses this chapter's current tiles. The right uses an optional revision with fewer soil marks and broader rock ledges. Both panels put the same walkers at the same coordinates. Neither panel is a capture of the simulation.
Figure 18.5: current atlas on the left, optional revision on the right. The layout stays fixed so you can judge the texture without also judging a different pond. Look at a walker first, then let your eye move across the ground.
Each revised soil tile has eight marked pixels: six dark and two light, leaving 248 midtone pixels. Those counts belong to this study; the chapter's original soils still have 37 marks. The revision keeps the same sixteen-color palette and sixteen-pixel cells. North and west banks stay dark, east and south stay lit. Its four-bit mask still misses diagonal-only land, exactly as the current sheet does.
Figure 18.6: the optional
64 by 80 PNG atlas.
Cell IDs remain 0–15 for water masks, 16 and 17 for soil, 18 for rock and 19 for
alternate open water. Every pixel is opaque; the PNG retains the alpha channel
LoadSheet requires.
Download the atlas and comparison image below. Save the atlas as assets/studies/quiet-tileset.png in your project, then check its palette and seams with the palcheck tool you wrote.
go run ./cmd/palcheck -pal assets/palette/valley-16.gpl -cell 16 -seams assets/studies/quiet-tileset.png
Edit one soil cluster in the PNG, then check its palette and seams. Keep this study
under assets/studies/: the seed-5 screenshots and pixel hashes in this volume
still use assets/tiles/valley-tileset.png. Replacing that file also
requires regenerating the renderer's figures and hash fixtures.
The finished client can select this sheet without replacing the original. The runtime comparison uses the same seed, tick and walkers with each atlas, and includes commands to reproduce both pictures. The fixed layout above stays an authored study; those runtime pictures come from the running simulation.
Checkpoint
- Given a water cell and the four cells around it, compute its mask and name the cell of the sheet that draws it.
- Explain why sixteen tiles ship when six drawings would cover every case, in terms of both the light direction and the code that would otherwise rotate them.
- Draw a second variant of a ground tile that breaks the repeat, and name the two counts it matches.
- Given a field of one tile alternated with another, say whether the fault is a distinctive feature or a mismatched weight.
- Say why the bank is dark on a tile's north side and pale on its south, and what rotating that tile would do to the sun.
- Read both seam rules' complaints and tell a broken join from a tile that disagrees with its own mask.
Exercise 1: five cells, five numbers. Work out the mask for
every water cell in this patch, where w is water and . is
soil, then lay those tiles side by side in the editor and check the shoreline runs
all the way round.
.....
.www.
.ww..
.....
Top row, left to right: the first cell has land north and west, so 1 + 8 = 9. The middle has land north only: 1. The third has land north, east and south, since the cell below it is soil: 1 + 2 + 4 = 7. Bottom row: the left cell has land south and west, 4 + 8 = 12; the right has land east and south, 2 + 4 = 6.
Cells 9, 1, 7, 12 and 6 of the sheet. Paste them into a spare canvas in that arrangement: the dark bank runs across the top of all three upper tiles and down the left of both tiles in the first column, and the pale shallows run along the bottom of every tile with soil under it. Cell 7 is the one to look at, since three of its four sides are land, so it draws the one-cell inlet at the pond's east end. All five masks appear in figure 18.4.
Exercise 2: put reeds in the wrong place. Paint six pixels of
green-m across the bottom two rows of the open-water tile, columns 5 to 7,
export as assets/tiles/reeds.png and run the checker. Predict how many
complaints one six-pixel mark produces.
$ go run ./cmd/palcheck -pal assets/palette/valley-16.gpl -cell 16 -seams assets/tiles/reeds.png
assets/palette/valley-16.gpl: 16 colors loaded
assets/tiles/reeds.png 64x80 5120/5120 opaque 10 colors all on palette
pixels sha256 88bce779c7af58b258765ceefc34cc43741be90d294dd0223f9ded664adba285
20 cells of 16, 4 across
seams: 172 joins (2752 pixel pairs), 68 shore edges
cell 0 above cell 0: green-m meets water-m at column 5
cell 0 above cell 2: green-m meets water-m at column 5
cell 0 above cell 4: green-m meets water-m at column 5
cell 0 above cell 6: green-m meets water-m at column 5
cell 0 above cell 8: green-m meets water-m at column 5
cell 0 above cell 10: green-m meets water-m at column 5
cell 0 above cell 12: green-m meets water-m at column 5
cell 0 above cell 14: green-m meets water-m at column 5
cell 0 above cell 19: green-m meets water-m at column 5
cell 0 (mask 0, land none): the S edge is not open water
seams: 10 DISAGREEMENT(S) with the sheet's own terms
Ten, from six pixels. Nine of them are rule one: open water can sit above any tile whose north side claims no land, and there are nine such tiles, so one bad row breaks nine different joins. The tenth is rule two arriving at the same pixels from the other direction, because a tile claiming no land to the south has to hold open water along that edge and now holds greenery. Reeds are a fine idea and this is the wrong place for them: they belong on a land tile leaning over the bank, or on their own tile with the mask arithmetic extended to say where it goes.
Exercise 3: give rock what soil got. Draw a second rock tile so a wide outcrop stops repeating. Match it to the first the way soil B was matched to soil A, add it to the sheet, and get it past the checker.
Count the original first: 21 pixels of rock-d and 10 of
rock-l, arranged as five lit-and-shadowed ledge pairs and three loose
dark runs. Aim for the same two counts and the same number of pairs, move every one
of them somewhere else, and keep each ledge leaning the same way, because a ledge
lit from the lower right in one tile out of two is the checkerboard from figure 18.2
with the light doing the alternating.
The sheet grows a row: 4 by 6 is 24 cells, the new tile lands at index 20, and
CellCount goes to 21. Rule one picks it up as soon as
landMaterial answers "rock" for the new index, which is one constant
and one line, and the pair loop finds every join it can make with the old tile on
its own. Run the checker, then tile a ten-by-ten field of the two and look at it: if
you can find where one tile ends, the two are not matched yet.