← mino.mobi

arnold

a square filling a cube — every colour equally often, no seams

part of the pack · geometry · voronoi · cohomology · kakeya · heilbronn

Pixel (x, y) is coloured by a continuous map from the square onto the RGB cube. That is impossible to do smoothly: a square has two dimensions to spend and a cube costs three, so the best exponent any such map can have is 2/3, and until 2026 nobody knew whether 2/3 itself was reachable (Arnold's problem 1988–5). Badger and Palmer reached it. This page renders their surface, folded so that every colour appears equally often, then bends it with area-preserving maps of the torus so a seed makes a family. Drag to pan, wheel to zoom: it is the same surface at every scale.

This page draws the surface on the GPU and needs WebGL2, which this browser does not expose. The census and print tabs still work on the CPU.

Strength 0 is the bare surface of the paper. Every setting lives in the URL, so a good one is a link.

The claims are countable, so count them. The engine renders the current seed on the CPU at a chosen size, quantises to a colour cube whose volume divides the pixel count, and reports how often each colour occurs and how far apart neighbours are. At the sizes marked exact, the bare surface hits every colour precisely the same number of times.

Render the whole torus tile of the current seed as a PNG. At the exact sizes the channels are quantised to the number of levels the count works for, so the file is the census: every colour in it appears the same number of times, and no two neighbours differ by more than a couple of levels.

Rendered on the GPU, so it takes a moment and a 4096² file is about 64 MB of pixels before compression. The print ignores the view: it is always the full tile, which repeats seamlessly in both directions. Exactness holds for the bare surface (strength 0, no cat map); warps keep the histogram flat in the limit but not to the pixel.

Watch it get built. Each pixel is one run of the same four steps: its two coordinates each walk the staircase curve into the X-shaped fractal, Stong's map adds the two x-coordinates and keeps the two y-coordinates, the fold lays the sum flat, and the palette names the channels. Scan paints pixel by pixel with the machinery shown for the pixel in hand. Refine paints the whole square at depth 1, then 2, then 3, then 5: the same algorithm, allowed one more octal digit each pass.

g(u) — the x coordinate walks the X

g(v) — the y coordinate walks the X

L, folded — where the pixel lands in the cube

Uses the seed, strength, cat map, palette and red-channel mode from the surface tab, on a 256² canvas. At depth 1 the staircase has one octal digit and the image is 32 blocks a side; depth 2 is one block per pixel here; deeper passes only move colours by a level or two, which is the Hölder condition again.

the problem

A continuous map from the square onto the cube exists by Hahn–Mazurkiewicz, but that argument says nothing about how violently it must tear distances. Measure the violence with a Hölder exponent: ‖f(x)−f(y)‖ ≤ H‖x−y‖α. Larger α means gentler. Cut the square into k² cells; each image has diameter at most Hk−α, and those k² blobs must cover a cube of volume one, so k² · (Hk−α)³ ≥ 1 for every k, which forces α ≤ 2/3. Arnold asked in 1988 whether 2/3 is attained. Shchepin got every exponent below it in 2010. Badger and Palmer's Space-filling surfaces (arXiv:2608.21246, August 2026) attains it, with the explicit constant ‖f(x)−f(y)‖∞ ≤ 48‖x−y‖∞2/3.

For a picture the exponent is a budget, not a style. A square image of side N whose neighbours differ by at most d colour levels can reach at most about (N · d)2/3 distinct values per channel. Hitting all of 256³ colours with every one used equally often, on a square, with small d, is exactly the α = 2/3 case. Anything smoother must miss colours.

the construction

The engine in surface.js runs the paper's map in four steps.

  1. A staircase curve. In the isosceles right triangle with hypotenuse [0,1], nine points climb from (0,0) to the apex (½,½) in steps of ¼ and descend to (1,0). Each of the eight steps is the hypotenuse of a smaller copy of the triangle, so the curve refines itself: eight parameter steps for a spatial factor of four, 82/3 = 4. That is the exponent, before anything three-dimensional has happened.
  2. An X. Four rotated copies of the staircase, concatenated, fill the self-similar set you get by keeping only the 8 of the 16 sub-squares that meet a diagonal, then repeating. It has dimension 3/2, it is a closed curve, and the parameter is uniform on it: each of the eight sub-squares gets exactly an eighth of the parameter.
  3. Square it. (u, v) ↦ (g(u), g(v)) sends the square into the product of two X's, a 3-dimensional set in ℝ⁴. This is where the two dimensions become three, and it is trivially continuous: no stitching, no seams. The cheap trick of composing a plane-filling curve with a cube-filling curve fails precisely here, because plane-adjacent points can be far apart along the curve.
  4. Stong's linear map. L(a₁,b₁,a₂,b₂) = (a₁ + 2a₂, b₁, b₂). Linear, hence continuous, hence the whole map is 2/3-Hölder. The miracle, due to Stong (1998), is that on the lattice points of the X the base-4 digits of a and b agree or complement each other in every place, and then a₁ + 2a₂ runs through every residue class exactly once. L is a bijection from lattice pairs onto ℤ³.

what the paper does not do, and what this page does

The paper needs an onto map with a clean constant, so it takes the image of L, a box of size 3×1×1, clips it to a small central cube, and rescales. For a picture that is a catastrophe: 98% of the square lands on the clipped faces. The paper also never claims measure preservation, and the clipped map has none.

So this page folds instead. The first coordinate P = a₁ + 2a₂ is the sum of two independent uniforms on [0,1] and [0,2], which is a trapezoid on [0,3]: ramp up, plateau at 3/2, ramp down. A triangle wave of period 2 lays the two ramps on top of each other and they add up to the plateau. So (fold P, b₁, b₂) has a flat histogram in the limit, and folding is continuous, so the exponent survives. The joint flatness, not just the marginal, is what the lattice bijection buys: at image sizes aligned to the lattice the count is exact to the pixel, which the selftest holds it to and the census tab lets you check. The fold's phase is not free: any other crease misaligns the ramps and the histogram develops holes.

imagecolour cubepixels per colourcountedmax neighbour jump
256²16³16exactly 16, every colour2 of 16
512²32³8exactly 8, every colour2 of 32
1024²64³40 to 8, 99.9% of colours hit2 of 64
2048²64³16exactly 16, every colour2 of 64
4096²128³8exactly 8, every colour2 of 128
8192²256³40 to 8, 99.95% of colours hit (offline, 67M pixels)2 of 256

The 8192² row is the regime of the image that prompted this page, which was described as every RGB value between 1 and 10 times, average 4, neighbours within 4/255. The surface here does the same job slightly tighter. The rule for exactness is that the image side is 4·8k or twice that and the cube side is 4k or twice that, so every pixel sits inside one lattice square and every lattice square inside one colour cell.

what is rigid, and where the family comes from

The surface itself is one object. Its symmetries are the symmetries of the image: the parameter curve is closed, so the square is a torus and the image tiles; rotating the X shifts the tile by a quarter; reflecting it flips the tile. Zooming into any cell shows the whole surface again, rotated and linearly recoloured, which is why the zoom never runs out. None of that makes a second image.

Two things do, and both keep the histogram flat by construction.

What does not work: exact-histogram post-processing. Sorting pixels into slices to force every count to 4 moves colours by up to 8 levels and lifts the neighbour jump from 2 to 12. The continuum object is flat; the discrete render is flat exactly where the lattice says so and within a few counts elsewhere. That is the honest statement and it is what the census reports.

the render

The GPU evaluates the map per pixel from a 32-bit fixed-point torus coordinate: two bits choose which copy of the staircase, ten octal digits walk the similarities, and the walk finishes at the centre of the last square, which is exactly where every quarter-triangle's apex is. Warps are integer adds of a float displacement, so zoom stays precise to about 2−14 of the tile. The CPU engine in surface.js does the same in doubles; the census uses it, the selftest imports it, and the GPU = CPU? button compares the two.

References: Badger & Palmer, arXiv:2608.21246; R. Stong, Mapping ℤʳ into ℤˢ with maximal contraction, Discrete Comput. Geom. 20 (1998); V. I. Arnold, Arnold's Problems, 1988–5; E. V. Shchepin, On Hölder maps of cubes, Math. Notes 87 (2010). Engine and checks: arnold/surface.js, node arnold/surface.selftest.mjs.