surt91 invited me to write a guest post and left the topic up to me. I went looking for something that needs almost no code and still earns a second look — and landed on Truchet tiles.

There is exactly one tile. It is a square carrying two quarter-circle arcs, each joining the midpoints of two adjacent edges. That is the entire inventory. The only freedom is how it sits on the grid, and there are exactly two options:

The two orientations of the single tile: the arcs hug either the top-left and bottom-right corners or the other two

Drop many copies onto a grid and flip a coin for each one. Out of this almost insultingly simple rule falls this:

Truchet tiling of randomly rotated quarter circles

It gets me every time. Nothing in the rule knows anything about loops, symmetry or closed curves, and yet the arcs reach across the tile boundaries and weave into a tidy fabric. The idea is old: the Dominican friar Sébastien Truchet worked through the patterns a single square produces in all its rotations back in 1704. His original tile was still a square split diagonally into a black and a white half; the smooth arc variant that gives these flowing lines is due to Cyril Stanley Smith, who dug the Truchet tiles back up in 1987.

The whole trick fits into a handful of lines of Python that print an SVG directly — no numpy, no plotting framework, just a bit of geometry and random:

import random

def truchet(filename, n=16, s=40, seed=42, stroke_ratio=0.18):
    random.seed(seed)
    w = h = n * s
    r, sw = s / 2, s * stroke_ratio
    paths = []
    for j in range(n):
        for i in range(n):
            x, y = i * s, j * s
            tm = (x + r, y)      # top middle
            rm = (x + s, y + r)  # right middle
            bm = (x + r, y + s)  # bottom middle
            lm = (x, y + r)      # left middle
            if random.random() < 0.5:
                # arcs hug the top-left and bottom-right corners
                arcs = [(tm, lm, 1), (bm, rm, 1)]
            else:
                # ... or the top-right and bottom-left ones
                arcs = [(tm, rm, 0), (lm, bm, 1)]
            for (ax, ay), (bx, by), sweep in arcs:
                paths.append(f'M{ax:.1f} {ay:.1f} '
                             f'A{r:.1f} {r:.1f} 0 0 {sweep} {bx:.1f} {by:.1f}')
    body = '\n'.join(f'  <path d="{d}"/>' for d in paths)
    svg = (f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}" '
           f'width="{w}" height="{h}">\n'
           f'<g fill="none" stroke="#000" stroke-width="{sw:.1f}" '
           f'stroke-linecap="round">\n{body}\n</g>\n</svg>\n')
    with open(filename, 'w') as f:
        f.write(svg)

truchet('truchet.svg')

Per tile we draw two quarter-circle arcs as an SVG path, and the only decision is the coin flip picking which pair of corners they hug. The 1 or 0 is the arc’s sweep flag; all it does is make the arc bulge inward instead of outward. That was the entire algorithm.

Look closely and the tangle turns out not to be a tangle at all: at every edge midpoint exactly two arcs meet — one from each of the two neighbouring tiles. So every stroke has precisely two neighbours, and the whole mess comes apart cleanly into a handful of closed loops. Give each of them its own colour — a short union-find over the edges does the job — and you can see how wildly their lengths differ:

The same tiling, each closed loop in its own colour

Some loops meander all the way across the image, others have shrivelled down to a single small circle. Which of the two is decided entirely by the seed.

When I showed surt91 the finished tiling, his first reaction was that it reminded him of percolation. He is right — the random arc tiling is in fact one of the standard ways to draw critical percolation. Because every tile always carries two arcs, the coloured loops from before are nothing but the hulls of percolation clusters. And the fair coin with probability $1/2$ lands, thanks to self-duality, exactly on the critical point $p_c = 1/2$ of the square lattice. That is precisely why loops appear on every scale — at the phase transition the system is scale invariant.

So the loop-size distribution inherits everything we know about critical percolation. The loops are fractals of dimension $7/4$ — the percolation counterpart to the $187/96$ that already turned up here for the Ising model (a German post). The number of loops enclosing an area larger than $A$ is even known exactly and universally, namely $\frac{1}{8\pi\sqrt{3}}\,\frac{1}{A}$, a pretty result of Cardy and Ziff. And the best part, because it confirms surt91’s hunch outright: bias the coin so that one orientation comes up more often, and you step off the critical point. The big loops meandering across the image vanish in favour of a characteristic maximum size — only at exactly $1/2$ does the spectrum reach all the way to the edge of the picture. In the generator that is a one-line change: random.random() < p instead of < 0.5.

There is no point in a GitHub repo for any of this; the full code is already up there in the post. A different number in the seed, and you have your next wallpaper. Which makes this guest post fit neatly into this blog’s well-documented fondness for black-and-white pictures of lines and circles — a German post, but the pictures need no translation.