April 2, 2026

How Daily Unfold Actually Simulates Paper Folding

The grid representation, the fold transform, the punch propagation, and the one bug that ate a whole weekend. A technical deep-dive into the core engine.

The core question

If you fold a square piece of paper three times and punch a hole in the resulting stack, how many holes are there when you unfold it, and exactly where do they land? That’s the question the Daily Unfold engine has to answer.

The naive answer is “one punch × 2³ layers = 8 holes.” That’s right as a ceiling, but it’s wrong as a rule — the actual number depends on where the punch lands relative to the fold lines. A punch directly on a fold line only reflects to itself. A punch that crosses the folded edge of the paper might only punch through fewer layers than expected.

To answer it correctly, we simulate.

The grid representation

We model the paper as an N×N grid of cells, where N is 4 for easy puzzles and 6 for medium and hard. Each cell can be either present (paper occupies it) or absent (the paper has been folded away from this position). Coordinates run from (0, 0) in the top-left to (N-1, N-1) in the bottom-right.

Every cell also tracks a list of original positions — the cells in the unfolded paper that map to this folded location. Initially, each cell’s list is just itself: cell (2, 3) tracks “(2, 3)”. After a fold, the cells in the unfolded half get appended to the cells they land on in the folded half.

So a folded paper is two data structures, kept in sync:

  • A shape mask — which cells still exist after folding.
  • A provenance map — for each live cell, the list of original grid positions that folded onto it.

The fold transform

A fold has three parameters: an axis (vertical or horizontal), a position (where the fold line falls), and a direction (which half moves over which). In Daily Unfold, folds always come toward you — the direction is fixed — which halves the state space and eliminates a source of ambiguity.

For a vertical fold at column c:

  • Every cell in the moving half gets reflected across the fold line: column x maps to column 2c - 1 - x.
  • The reflected cell’s provenance list is appended to the provenance list of the cell it lands on in the stationary half.
  • The moving half is then marked as empty.

Horizontal folds are the same, but along the row axis. Three folds in sequence compose naturally — each operates on the already-folded state.

Punch propagation

Once all folds are applied, we punch. The punch position is a single cell in the folded paper’s live region. The answer is simple: the holes in the unfolded paper are exactly the original positions stored in that cell’s provenance list.

This is why the provenance map is the whole trick. The simulation never actually “unfolds” anything — the provenance map already knows which unfolded cells are stacked where. A punch just reads off that list.

The weekend-eating bug

Early in development I shipped a bug where certain hard puzzles would produce mirrored but subtly incorrect solutions. The symptom: roughly 1 in 30 generated puzzles had a hole in the “wrong” reflected position. Not obviously wrong — just off by one cell in one direction.

The cause was off-by-one in the reflection formula. I had written 2c - x instead of 2c - 1 - x. For folds at integer columns, this placed the reflected column one cell too far, but only in certain configurations. It passed every hand-written test case I had because those tests happened to use even-indexed fold lines where the difference cancelled out.

The fix was two characters. The lesson was “fuzz your simulation with random inputs and compare to a physical paper model.” I spent a Sunday with actual paper, a ruler, and a hole punch, confirming that the engine agreed with reality across a hundred random inputs. It didn’t, and now it does.

Why a grid?

The grid representation is a simplification — real paper folds continuously, and there’s no reason a punch should land exactly on a grid cell. We chose the grid model for three reasons:

  • Discreteness maps to player UX. Users predict by tapping cells. A continuous model would need a “close enough” scoring function, which is fuzzy and harder to explain.
  • Determinism. A grid state is trivially serializable and comparable. Two puzzles with the same fold sequence and punch position produce identical results every time, on every device.
  • Difficulty control. Grid size becomes a direct difficulty knob. 4×4 with one fold is approachable. 6×6 with three folds is genuinely hard for most people.

What this enables

Because the engine is fast and deterministic, we can procedurally generate thousands of candidate puzzles per second and score each one for difficulty. That’s what makes the daily pipeline work: the generator proposes, the scorer rates, the top candidates per difficulty tier get kept. The whole ceremony happens at build time for every future day.

The difficulty scoring is its own rabbit hole, which I wrote about separately: Modeling What Makes Paper-Folding Puzzles Hard.

Closing thought

The engine is maybe 300 lines of TypeScript. The interesting engineering is not in the code — it’s in deciding what to model and what to throw away. Once we committed to the grid representation, most of the implementation wrote itself. The harder question was whether the grid would feel like paper when you played it, and that was only answerable by shipping.