The third colour: a modulo-3 phase for an endless tipping-block puzzle
12 November 2026
Abstract
This paper studies a small endless rolling-block puzzle. A domino made of two cubes (a \(1\times1\times2\) block) tips and rolls on a finite patch of floor; the artifact uses a \(13\times9\) coordinate box. The only win is to stand the block on the hole. A lying block may rest with one end over the hole; it never wins there. The block's pose is the state, the floor is carved by a random walk, and every generated level is solvable by replaying that walk. Section 2 explains the whole game in plain words, with diagrams. We then give a simple formal model, a breadth-first solver, and a small piece of mathematics called the phase: a pair of remainders attached to every pose. Tips keep the phase; side-rolls change one coordinate by one. This gives a quick impossibility test, a formula for straight corridors, and a lower bound on the number of rolls. A generator and a seeded sample set show where the phase is sharp and where it is not: the single colour \((x+y)\bmod 3\) is weaker than the pair, a matching phase is not sufficient, and the roll bound is weak. The playable artifact is embedded in Section 7.
Keywords: rolling-block puzzle · modular colouring · phase invariant · random generation · playable artifact
1. Introduction
Block Escape is a small endless version of the rolling-block genre, distilled from Bloxorz1. There are no switches, trapdoors, keys, or fixed level list. A \(1\times1\times2\) domino — two cubes glued together — stands, tips, and rolls on a finite patch of floor; one tile is the hole. A move is legal when every cell the block covers after the move is floor, except that one end of a lying block may be the hole. Standing exactly on the hole wins; a lying block over the hole is a rest, not a win.
If you are new to the game, start with Section 2. It explains the board, the block, every move, falling, winning, the controls, and how levels are generated, with pictures. The later sections add the formal rules, the solver, and the modulo-3 argument.
Each level is generated, not authored. Level \(n\) is a random walk of \(\min(10+2n,40)\) moves, extended until the block ends standing. Every cell the walk touches becomes floor, the final standing cell becomes the hole, and the length of the walk is the par. Replaying the generator's walk is a legal solution, so every emitted level is solvable and the supply never runs out.
Section 4 makes the rules operational: a level becomes a finite graph whose vertices are poses, and breadth-first search returns the shortest winning word. That is the model used to solve the levels; the generator's walk is only one witness path.
The paper asks what modulo \(3\) actually decides. The useful object is the phase pair \[ \begin{aligned} \varphi(U(p)) &= p\bmod 3,\\ \varphi(L(p,p+d)) &= (p-d)\bmod 3 . \end{aligned} \] not one colour. Tips keep the pair; a side-roll changes one coordinate by \(\pm1\) and needs a \(2\times2\) floor patch. On a board without such a patch the phase is invariant, so a hole with a different phase is impossible. A mismatch with a patch is a demand for rolls, not an impossibility. The phase is exact on a straight corridor and a lower bound on rolls elsewhere.
The artifact in Section 7 is the same endless game: arrow keys or WASD,
R restart; N next level after a win.
The game shows your move count next to Best, the shortest possible
solution found by breadth-first search. The generator's walk is only a
witness; the par in the table below is its length.
2. How to play
The whole game, in one paragraph. You control a block made of two cubes glued together. It stands on one tile or lies across two. Tip it, stand it up, or roll it sideways with the four arrow directions. The board has floor tiles, empty tiles, and exactly one hole. Your only job is to make the block stand upright on the hole. If a move would put any part of the block on empty space, it falls; it returns to the last safe position and you try again.
This section is the game in plain words. Section 3 repeats the same rules formally, with symbols, and Sections 4–6 explain the solver, the colour invariant, and the level generator. A reader who only wants to play can stop after this section. The diagrams are wide; on a narrow screen, scroll a figure sideways to see its whole key.
2.1 The board: tiles, floor, void, and the hole
The board is a grid of square tiles. A coordinate pair \((x,y)\) names one tile: \(x\) is the column, \(y\) is the row. The artifact's coordinate box is \(13\times9\), so \(x=0,\ldots,12\) and \(y=0,\ldots,8\). That is \(13\cdot9=117\) possible positions — but it is not 117 floor tiles.
Each tile is one of three things:
- Floor: the block may stand or lie on it.
- Void: there is no floor; the block cannot use it.
- The hole: one special tile that is not floor. Standing on it wins. A lying block may rest one end over it, but never wins that way.
The generator decides which tiles are floor. Most of the coordinate box can be void. The block starts standing on a floor tile at the centre; in the artifact that is \((6,4)\).
2.2 The block: standing or lying
The block is rigid. It never bends, stretches, or jumps. It has exactly three kinds of footprint:
- \(U(x,y)\) — standing: one tile, \((x,y)\).
- \(H(x,y)\) — lying east–west: two tiles, \((x,y)\) and \((x+1,y)\).
- \(V(x,y)\) — lying north–south: two tiles, \((x,y)\) and \((x,y+1)\).
The physical block is two cubes high when standing, but height never affects a move. Only the tiles underneath matter. For example, \(U(6,4)\) is the start tile, \(H(6,4)\) covers \((6,4)\) and \((7,4)\), and \(V(6,4)\) covers \((6,4)\) and \((6,5)\).
2.3 A swipe: tip, stand up, or roll
A swipe is one of four directions: north \(N\) (increase \(y\)), east \(E\) (increase \(x\)), south \(S\) (decrease \(y\)), or west \(W\) (decrease \(x\)). The block pivots about the edge it is touching. There are only three move shapes:
- Tip over: a standing block falls flat in the swipe direction.
- Stand up: a lying block stands on the far end if swiped along its length, or on the near end if swiped the other way.
- Roll sideways: a lying block shifts one tile perpendicular to its length, staying flat.
One keypress or swipe attempts exactly one move. The block can end in a different orientation; the next swipe starts from that new pose. There is no sliding, no jumping, and no turning in place. Section 3, Table 4 gives all twelve before/after cases.
2.4 Safe, fallen, and winning
After a move, look at every tile the block covers. Each of those tiles must be floor. There is one exception: a lying block may have one end over the hole, as long as its other end is floor. This is a half-hole rest. It is safe, but it is not a win.
A win is only this: the block is standing, and the one tile it covers is the hole. Nothing else wins. In particular, lying across the hole never wins, even if both ends are over the hole (which cannot happen with one hole and a two-tile block, but the rule is still “standing only”).
A fall happens when a covered tile is void, or when a lying block has one end over the hole and the other over void. A fall does not end the level: the block returns to its last safe position, and the failed swipe still counts in your move total. A block standing on ordinary floor is safe.
Remember three checks. 1. Is every covered tile floor, or is one end of a lying block over the hole with the other end on floor? If not, fall. 2. Is the block standing on the hole? If yes, win. 3. Otherwise the move is safe; keep going.
2.5 One turn, and the controls
Each swipe is one turn. Use the arrow keys or W/A/S/D, or drag the board in
the direction you want. R restarts the same level;
N starts the next level after a win; you cannot skip ahead.
Completed levels stay available in the level strip. After a win, any key
starts the next level. There is no timer, no score penalty beyond your move count, and no
undo button: a fall simply puts the block back and you try another
direction.
The display shows the level number, your move count, and Best — the shortest solution found by search. When you win, the clear card compares your moves with Best. Matching Best is optimal; taking more moves is still a valid escape.
2.6 Where a level comes from
Levels are generated, not hand-designed. For level \(n\), the game starts the block standing at the centre. It takes a random walk of in-bounds swipes. Every tile the block touches is carved as floor. The walk stops when the block is standing on a tile that was never touched before; that tile becomes the hole. Because the final tile is new, removing it does not break any earlier move. The walk itself is a legal solution, so every level is solvable.
The walk length is the par. It is one solution, not necessarily the shortest. A separate search finds the shortest solution; that length is the optimum. The game displays this optimum as Best, next to your moves. The generator's target walk length is \(\min(10+2n,40)\): it grows until level 15, then stays at 40. Later levels are still random, so they are not identical, but after level 15 the target no longer grows. The live game uses a fresh random walk for each new level.
2.7 The modulo-3 idea, in one paragraph
There is an optional three-colour pattern on the tiles. The full version is not one colour but a pair of remainders, \((\,x\bmod3,\;y\bmod3\,)\). A tip keeps that pair; a sideways roll changes exactly one coordinate by one and needs a \(2\times2\) patch of floor or hole. This can prove that some hole positions are impossible, but a matching pair does not guarantee a solution. Section 5 gives the exact statement.
2.8 Words used in the paper
| Word | Meaning |
|---|---|
| Tile / cell | One square of the coordinate box, named by \((x,y)\). |
| Floor | A tile the block may use. |
| Void | A tile with no floor; the block cannot use it. |
| Hole | The single target tile; not floor for standing, but one end of a lying block may rest over it. |
| Pose | The block's footprint: \(U\) standing, \(H\) lying east–west, \(V\) lying north–south. |
| Anchor | The west or south tile that names a lying pose; the single tile for \(U\). |
| Swipe | One attempted move in direction \(N,E,S,W\). |
| Tip | Standing to lying. |
| Stand up | Lying to standing. |
| Roll | Lying to lying, shifted sideways. |
| Safe | Every covered tile is floor, or one end of a lying block is over the hole with the other on floor. |
| Fall | A move that is not safe; the block returns to its last safe pose. |
| Win | Standing on the hole. |
| Par | The length of the generator's walk, a guaranteed solution. |
| Optimum | The shortest possible solution, found by search. |
| Level | A generated floor set plus one hole and a start pose. |
| Phase | The pair \((x\bmod3,y\bmod3)\) attached to a pose; used for the colour argument. |
3. The rules
This section is the formal version of Section 2. A level has a finite floor set \(B\), one hole \(h\notin B\), and a start pose \(s\). The artifact uses the \(13\times9\) coordinate box from Section 2.1. Table 2 collects the notation, Table 3 lists the three poses, and Table 4 lists the move cases.
| Symbol | Meaning |
|---|---|
| \(M=(B,h,s)\) | level: floor set \(B\), hole \(h\), start pose \(s\) |
| \(B\subseteq\mathbb Z^2\) | finite set of floor cells |
| \(h\in\mathbb Z^2\setminus B\) | hole cell |
| \(x,y\) | tile indices: \(x\) column, \(y\) row; \(p=(x,y)\) is a tile |
| \(U(p),H(p),V(p)\) | standing, lying east-west, lying north-south poses anchored at \(p\) |
| \(L(p,p+d)\) | lying pose anchored at \(p\) extending in direction \(d\) |
| \(N,E,S,W\) | unit direction vectors |
| \(D\) | direction set \(\{N,E,S,W\}\) |
| \(d,e\) | directions; \(e\perp d\) means perpendicular |
| \(c\) | a cell |
| \(\operatorname{cells}(b)\) | cells occupied by pose \(b\) |
| \(\operatorname{legal}(b)\) | pose supported by floor, with the partial-hole exception |
| \(\operatorname{win}(b)\) | \(b=U(h)\) |
| \(\operatorname{step}(b,d)\) | pose after swiping pose \(b\) in direction \(d\) |
| \(b,q\) | generic poses |
| \(Q\) | breadth-first search queue |
| \(w,\varepsilon\) | a word of directions; \(\varepsilon\) is the empty word |
| \(\bot\) | no solution |
| \(\varphi(b)\) | phase pair of pose \(b\) |
| \(\mathbb Z,\mathbb Z_3,\mathbb Z_3^2\) | integers, residues modulo 3, and pairs of residues |
| \(\chi(p)\) | single colour \((x+y)\bmod3\) |
| \(n\) | level index |
| \(s_0\) | start pose when it is standing |
| \(\operatorname{par}(M)\) | generator walk length |
| \(\operatorname{opt}(M)\) | BFS shortest-solution length |
| R, N, WASD | keyboard controls: restart, next level after a win, movement (see Section 7) |
The table is also the legend for the symbols used in the figures, tables and code below. The Lean sketch writes \(c\) for a cell and \(b\) for its block argument; \(M\) is always a level. The set \(B\cup\{h\}\) is the floor together with the hole; a pad is a \(2\times2\) square of cells in that set.
| pose | footprint | kind |
|---|---|---|
| \(U(x,y)\) | \(\{(x,y)\}\) | standing, two cubes high |
| \(H(x,y)\) | \(\{(x,y),(x+1,y)\}\) | lying east–west |
| \(V(x,y)\) | \(\{(x,y),(x,y+1)\}\) | lying north–south |
A swipe is one of \(N,E,S,W\). The rigid pivot gives exactly three move shapes:
| before | swipe | after | shape |
|---|---|---|---|
| \(U(p)\) | \(d\) | \(L(p+d,p+2d)\) | tip over |
| \(L(p,p+d)\) | \(d\) or \(-d\) | \(U(p+2d)\) or \(U(p-d)\) | erect |
| \(L(p,p+d)\) | \(e\perp d\) | \(L(p+e,p+d+e)\) | roll |
Legality. In words: every tile under the block must be floor. A lying block gets one exception: one end may be the hole, provided the other end is floor. The formula just writes that: \[ \begin{aligned} \operatorname{legal}(b) \iff{}& \operatorname{cells}(b)\subseteq B\\ &\lor\bigl(b\text{ lying}\land h\in\operatorname{cells}(b)\\ &\qquad\land \operatorname{cells}(b)\setminus\{h\}\subseteq B\bigr). \end{aligned} \tag{1} \] \[ \operatorname{win}(b)\iff b=U(h). \tag{2} \] The first line is the normal case; the second line is the half-hole exception. Otherwise the block topples off and returns to its last safe position. The hole is not floor, but one end of a lying block may rest over it if the other end is floor. Such a rest wins nothing. A standing block on the hole wins, and that is the only win condition.
Generator. For level \(n\), start standing at the centre. Take a target number of random moves, keeping only landing positions inside the grid, and carve every touched cell as floor. Keep walking until the block ends standing. To make the walk replayable, require the final standing cell to be new (never touched before); otherwise removing it would break an earlier move. Make that cell the hole and remove it from the floor. The number of moves in the final walk is the par. The extra steps are for replay: removing a goal cell that an earlier move used would break that earlier move. The artifact uses a \(13\times9\) board and \(\min(10+2n,40)\) as the target; both are design parameters. Any finite grid and any nonnegative target are valid.
4. Four definitions and a solver
In plain words. A solver does not guess. It lists every pose the block can reach in one move, then every pose reachable in two moves, and so on. The first time it reaches “standing on the hole”, that path is a shortest solution. The graph below is just a bookkeeping device for that list.
Optional: precise Lean 4 specification
The four definitions are written below as a Lean 4 specification. The listing is self-contained: it fixes the pose type, the twelve-case step table, legal support, the winning pose, and the reachability relation.
import Mathlib.Data.Finset.Basic
import Mathlib.Data.Int.Basic
import Mathlib.Logic.Relation
inductive Dir | N | E | S | W deriving DecidableEq, Repr
inductive Orient | U | H | V deriving DecidableEq, Repr
abbrev Cell := Int × Int
structure Pose where
o : Orient
x : Int
y : Int
deriving DecidableEq, Repr
namespace BlockEscape
def Pose.cells : Pose → Finset Cell
| ⟨.U, x, y⟩ => {(x, y)}
| ⟨.H, x, y⟩ => {(x, y), (x + 1, y)}
| ⟨.V, x, y⟩ => {(x, y), (x, y + 1)}
def step : Pose → Dir → Pose
| ⟨.U, x, y⟩, .N => ⟨.V, x, y + 1⟩
| ⟨.U, x, y⟩, .E => ⟨.H, x + 1, y⟩
| ⟨.U, x, y⟩, .S => ⟨.V, x, y - 2⟩
| ⟨.U, x, y⟩, .W => ⟨.H, x - 2, y⟩
| ⟨.H, x, y⟩, .N => ⟨.H, x, y + 1⟩
| ⟨.H, x, y⟩, .E => ⟨.U, x + 2, y⟩
| ⟨.H, x, y⟩, .S => ⟨.H, x, y - 1⟩
| ⟨.H, x, y⟩, .W => ⟨.U, x - 1, y⟩
| ⟨.V, x, y⟩, .N => ⟨.U, x, y + 2⟩
| ⟨.V, x, y⟩, .E => ⟨.V, x + 1, y⟩
| ⟨.V, x, y⟩, .S => ⟨.U, x, y - 1⟩
| ⟨.V, x, y⟩, .W => ⟨.V, x - 1, y⟩
def legal (floor : Finset Cell) (goal : Cell) (p : Pose) : Prop :=
p.cells ⊆ floor ∨
(p.o ≠ .U ∧ goal ∈ p.cells ∧ p.cells \ {goal} ⊆ floor)
def win (goal : Cell) (p : Pose) : Prop :=
p = ⟨.U, goal.1, goal.2⟩
def Move (floor : Finset Cell) (goal : Cell) (p q : Pose) : Prop :=
∃ d : Dir, q = step p d ∧ (win goal q ∨ legal floor goal q)
def Solvable (floor : Finset Cell) (goal : Cell) (start : Pose) : Prop :=
Relation.ReflTransGen (Move floor goal) start ⟨.U, goal.1, goal.2⟩
end BlockEscape
Orient has the three constructors \(U,H,V\);
Cell is \(\mathbb Z^2\); Pose is an orientation
plus coordinates; Dir is \(D\). The function
step is the twelve-case pivot table. legal
includes the half-hole exception, win is standing on the
goal, and Solvable is reflexive-transitive reachability.
This listing is a specification; it is not machine-checked in this
artifact because the Lean toolchain is not bundled.
4.1 Level, poses and state graph
A level is \(M=(B,h,s)\). A pose \(b\) is admissible when it is legal or winning. The winning pose is always added, because \(U(h)\) is not legal: the hole is not floor.
The graph is just a list of poses and moves. Each safe pose is a node. Each legal swipe is an arrow to the next pose. The start is \(s\); the goal is \(U(h)\). A swipe that lands off the floor is a fall, so it gets no arrow: it returns the block to the same pose and cannot help a shortest solution. That is the whole graph.
4.2 Breadth-first solver
Every edge costs one swipe, so breadth-first search returns a shortest winning word:
if win(s): return ε
Q ← [(s, ε)]; seen ← {s}
while Q is not empty:
(b, w) ← dequeue(Q)
for d in D:
q ← step(b, d)
if win(q): return w·d
if q is a state and q ∉ seen:
seen ← seen ∪ {q}
enqueue(Q, (q, w·d))
return ⊥
Optional: runnable JavaScript and Python solvers
The executable form is the same loop. It returns the move word and its
length, and uses the game's fromCells, step and
key helpers:
function solve(M) {
const s = fromCells(M.start);
const Q = [{ b: s, w: "" }];
const seen = new Set([key(s)]);
for (let i = 0; i < Q.length; i++) {
const { b, w } = Q[i];
for (const d of ["N", "E", "S", "W"]) {
const out = step(M, b, d);
if (!out) continue;
if (out.result === "win")
return { moves: w.length + 1, path: w + d };
if (out.result === "safe" && !seen.has(key(out.pose))) {
seen.add(key(out.pose));
Q.push({ b: out.pose, w: w + d });
}
}
}
return null;
}
The same breadth-first search in Python, using only the standard library.
A level is a dictionary with solid, hole and
start; the result is a shortest move string.
from collections import deque
D = {"N": (0, 1), "E": (1, 0), "S": (0, -1), "W": (-1, 0)}
def cells(p):
o, x, y = p
if o == "U": return {(x, y)}
if o == "H": return {(x, y), (x + 1, y)}
return {(x, y), (x, y + 1)}
def landing(p, d):
o, x, y = p
if o == "U":
return {"N": ("V", x, y + 1), "E": ("H", x + 1, y),
"S": ("V", x, y - 2), "W": ("H", x - 2, y)}[d]
if o == "H":
return {"N": ("H", x, y + 1), "E": ("U", x + 2, y),
"S": ("H", x, y - 1), "W": ("U", x - 1, y)}[d]
return {"N": ("U", x, y + 2), "E": ("V", x + 1, y),
"S": ("U", x, y - 1), "W": ("V", x - 1, y)}[d]
def legal(level, p):
floor = {tuple(c) for c in level["solid"]}
hole = tuple(level["hole"])
covered = cells(p)
if covered <= floor:
return True
return (p[0] != "U" and hole in covered
and (covered - {hole}) <= floor)
def win(level, p):
return p == ("U", *level["hole"])
def solve(level):
start = ("U", *level["start"][0])
queue = deque([(start, "")])
seen = {start}
while queue:
p, word = queue.popleft()
for d in D:
q = landing(p, d)
if win(level, q):
return word + d
if legal(level, q) and q not in seen:
seen.add(q)
queue.append((q, word + d))
return None
The Python solver mirrors the graph exactly. It uses tuples
("U", x, y), ("H", x, y) and
("V", x, y) for poses; seen prevents revisiting;
and the queue order guarantees the first win is shortest. The JavaScript
loop above is the same algorithm used by the game.
Every graph edge is a legal swipe, and every legal non-falling swipe is an edge. BFS therefore discovers poses in increasing move count, so the first win is optimal. Falls are omitted because they change nothing; the state space is finite, so the search terminates. There are at most \(3(|B|+1)+1\) states, under 400 for the \(13\times9\) artifact.
Every non-goal move is undone by the opposite swipe, so the non-goal graph is undirected; the goal is terminal. Counting the shortest-path DAG gives the number of shortest words. The generator gives one solution, so the level is solvable by construction; its walk length is an upper bound on the optimum, usually strict. BFS returns \(\operatorname{opt}(M)\), not \(\operatorname{par}(M)\).
5. The modulo-3 idea
The math in one screen.
- Phase is just two remainders: \((x\bmod3,\;y\bmod3)\).
- A tip leaves the phase alone.
- A side-roll changes one remainder by \(\pm1\), and needs a \(2\times2\) patch of floor or hole.
- No \(2\times2\) patch? The phase cannot change. Different start and hole phases then prove the level is impossible.
- Straight corridor: two tips move the block three tiles. The hole is reachable only when its distance is a multiple of 3, and the shortest solution is \(2m/3\) moves.
- Rolls: each roll is one step on a \(3\times3\) wrapping grid. The number of rolls needed is at least the shortest distance on that grid.
The phase is a filter, not a complete solver: a matching phase still does not guarantee a solution.
Give every pose a phase: two remainders, one for the column and one for the row. For a standing block, use its tile. For a lying block, use the empty tile just behind it:
Read \(p\bmod3\) as “the two remainders of the tile's column and row.” There are only nine possible phases, \((0,0)\) through \((2,2)\). Projecting the pair to one number gives the single colour \(\chi(p)=(x+y)\bmod3\).
Example. The tiles \((0,0)\) and \((1,2)\) both have single colour \(0\), but their phases are \((0,0)\) and \((1,2)\). The pair sees a difference that the single colour hides.
5.1 Tips do nothing to the phase
A tip changes the pose but not the phase. The reason is the definition: a standing block at \(p\) tips to a lying block whose “behind” tile is still \(p\). A lying block stands up at the same phase it already had. So if a play uses only tips, the phase is fixed from start to finish.
5.2 Rolls move one step
A side-roll shifts a lying block one tile sideways. That changes exactly one of the two remainders by \(\pm1\). The roll also covers four tiles in a \(2\times2\) square, so all four must be usable: floor, or the hole as the one allowed exception.
5.3 What this tells you
1. A quick impossibility test. If the board has no \(2\times2\) patch, no roll is possible, so the phase cannot change. If the start and the hole have different phases, the level is impossible. Example: start phase \((0,0)\), hole phase \((2,0)\), no \(2\times2\) patch — no solution.
2. Straight corridors. In a one-tile-wide corridor, every move is a tip. Two tips move the block three tiles. So the hole must be 3, 6, 9, … tiles away, and the shortest solution is \(2m/3\) moves for distance \(m\). Example: \(m=6\) needs 4 tips; \(m=4\) is impossible.
3. A lower bound on rolls. Think of the phase as a position on a \(3\times3\) wrapping grid. A tip stays put; a roll moves one cell up, down, left, or right. So the number of rolls is at least the shortest distance on that grid. Example: a phase difference of \((1,2)\) needs at least 2 rolls.
That is the whole idea. The phase is not a complete solver, and the roll bound is often weak; but it quickly rules out some holes and explains the corridor pattern.
6. The generator and samples
In plain words. The game makes a level by taking a random walk and painting every touched tile as floor. It stops standing on a new tile and turns that tile into the hole. Replaying the walk is therefore a legal solution; its length is the par. The par is not the shortest solution: the walk may wander, and a search usually finds a shorter route.
The generator's walk is a solution, so every level is solvable. It is not a shortest solution: the par is the walk length, and breadth-first search usually finds a shortcut. The game exposes that shortest length as Best next to your moves. The sample table uses a fixed seed; the live game uses a fresh random walk for each layout.
| n | par | optimum | floor | pads | pair | 3-colour | walk rolls |
|---|---|---|---|---|---|---|---|
| 1 | 20 | 7 | 16 | 6 | no | yes | 4 |
| 2 | 26 | 7 | 21 | 9 | no | no | 4 |
| 3 | 17 | 7 | 18 | 8 | no | yes | 5 |
| 4 | 20 | 10 | 22 | 8 | no | no | 12 |
| 5 | 30 | 5 | 18 | 7 | yes | yes | 8 |
| 6 | 24 | 7 | 20 | 8 | no | no | 4 |
Row 1 is the level in Figure 6: a single colour match hides a phase mismatch, so the single colour is not enough. Row 5 has a matching pair yet the generator walk uses eight rolls; a match does not mean roll-free. The par is much larger than the optimum in every row; the walk is a witness, not a shortest path.
7. Playable artifact
The artifact is the endless game itself: a random walk on the default \(13\times9\) board, partial-hole support, no fixed levels. Open it directly if your browser blocks local iframes. The 3D board needs WebGL.
8. Verification
The endless rules, generator and phase are checked by
node tools/test_endless.js: it generates 500 seeded layouts,
replays every walk, checks that each placement is supported (one end of a
lying block may use the hole), that the final placement wins, that every tip
preserves the pair and every roll moves one coordinate, and that a lying
block over the hole is a rest, not a win. The sample table and Figure 6 come
from node tools/sample_endless.js and a
small SVG renderer; the optimum column is the BFS solver from Section 4.2.
The game is built by python3 tools/build_endless.py.
The game and the paper are both offline. The paper's mathematics is set with
KaTeX and the KaTeX Computer Modern fonts, vendored under
katex/. The game embeds Three.js and DM Sans.
9. Limits
In plain words. The colour test can rule some levels out, but it cannot solve a level by itself. The generator's par is a practice route, not the best possible route. The game is deliberately small: no switches, no keys, no enemies, and no fixed set of levels.
The phase is necessary, not sufficient. A matching pair still needs a search. The torus bound is weak for the same reason: the phase has only nine classes. The generator's par is not an optimum; it is a witness produced by a random walk. Partial-hole support is the stated rule: a lying block may rest with one end over the hole, but only standing on it wins. The board size and walk target are parameters; the artifact uses \(13\times9\) and \(\min(10+2n,40)\). The game has no switches, trapdoors, height, or moving blocks. The four Lean-style definitions and the state graph are the specification; the breadth-first solver and the JavaScript game are the executable references.
References
- T. C. van der Zanden and H. L. Bodlaender. PSPACE-completeness of Bloxorz and of Games with 2-Buttons. arXiv:1411.5951, 2014.
-
Liminal Math. Block Escape, endless edition: the game source
game/logic.js,game/scene3d.js,game/app3d.js, andindex.html, 2026. -
Liminal Math. Generator and phase checker:
tools/test_endless.js,tools/sample_endless.js, andtools/build_endless.py, 2026.