'Pent-Up' Frustration 3 / Knight Moves 7
Jane Street’s July 2026 puzzle gives us an 8×8 board divided into thirteen regions. Each region gets one tower: an extra cube placed on one of its squares. A knight starts at the bottom-left corner and makes three-dimensional knight moves until it has visited every tower.
The knight also keeps a strange score. On move , a move at the same altitude adds to the score, a move upward multiplies the score by , and a move downward divides it by . Some scores are written on the board at fixed checkpoints. After reconstructing the path, the final answer comes from the squares the knight did not visit.

This was a good candidate for a small search program. There are too many possible tower placements and knight paths to explore directly, but the score arithmetic and the checkpoint schedule make most branches impossible almost immediately.
Reading the board as a 3D problem
The ordinary board is the ground plane at altitude 1. A tower square has altitude 2. A 3D knight move has coordinate differences 0, 1, and 2 in some order. Since the altitude can only change by 0 or 1, the legal moves on the 8×8 drawing reduce to three cases:
- At the same altitude, the knight makes an ordinary chess-knight move, changing one board coordinate by 1 and the other by 2. The score becomes
score + N. - From ground to a tower, the board displacement is a straight two-square move horizontally or vertically. The score becomes
score * N. - From a tower to ground, the board displacement is again two squares in a straight line. The score becomes
score / N, but only when the division is exact.
The last rule is particularly useful. A downward move is not merely another possible move; it is an arithmetic filter. At move 54, for example, the solution is already at score 1100 on the ground. The final move climbs onto the tower at (2, 7), so the score becomes 1100 * 54 = 59400.
The knight starts with score zero, so the first few moves are informative too. The only clue value that can be reached on move 3 is 1. The opening of the eventual solution is:
(7, 0) -> (6, 2) -> (5, 4) -> (5, 6)
0 1 3 1
The first two moves stay at altitude 2 and add 1 and 2. The third move descends and divides by 3. This identifies the clue 1 at (5, 6) as the move-3 checkpoint and, importantly, forces the starting square itself to be a tower.
Bounding the write interval
The first six written scores occur every three moves: moves 3, 6, 9, 12, 15, and 18. After that, the puzzle says the knight writes every K moves, with K > 3. There are five of these later checkpoints, so the last written score occurs at move 18 + 5K.
There are only 64 squares and the start counts as the first visited square. The knight can therefore make at most 63 moves. This gives
18 + 5K <= 63
Together with K > 3, only six values remain: K = 4, 5, 6, 7, 8, 9.
The clue squares are not just targets for the search. A checkpoint must land on a clue square with exactly the printed score, and an ordinary move is not allowed to land on a clue square at all. That lets the search match geometry and arithmetic at the same time.
Searching for the path
I represented a search state by the current move number, square, altitude, score, visited-square bit mask, and the partially assigned tower locations. The tower positions can be assigned lazily instead of guessed at the start.
When a branch lands on a square at altitude 2, that square becomes the tower for its region. If that region already has a tower, the branch is invalid. When a branch lands at altitude 1, the square is committed to the ground; if that would leave its region with no unvisited square available for a tower, the branch is also invalid.
This is the key simplification. The search does not need to enumerate all ways to place thirteen towers before it starts walking. The path itself gradually determines the tower placement:
landing at altitude 2 -> claim this square as the region's tower
landing at altitude 1 -> remove this square from the region's candidates
At each recursive call, the solver carries the move number, current cell, altitude, and score, along with the visited-cell mask, the towers assigned so far, and the clues already matched. It enumerates the eight same-altitude knight moves and the four straight two-square altitude moves. Before recursing, it applies the checkpoint rule: a checkpoint must land on its matching clue square with its matching score, while a non-checkpoint move may not land on any clue square. The journey is recorded only when all thirteen towers have been visited and all eleven clues have been consumed; the search stops at that point rather than extending the path unnecessarily.
The geometric pruning uses a breadth-first distance table over the twelve possible planar displacements, ignoring altitude. That distance is optimistic because it forgets tower placement and score divisibility, so it is safe as a lower bound. At a node, if no unused clue can be reached before the next checkpoint, the branch is discarded. There is also an arithmetic bound: over the remaining moves, even the largest possible sequence of divisions cannot take the current score below the largest remaining clue, so branches that are already too large can be rejected without exploring their descendants.
The final simple pruning check is that there cannot be more towers left than moves remaining. These bounds are cheap to evaluate and make the otherwise large search manageable.
The search is exhaustive over both possible starting heights and every possible K. Its result was unique:
K | Search nodes | Solutions found so far |
|---|---|---|
| 4 | 10,104 | 0 |
| 5 | 25,268 | 0 |
| 6 | 59,050 | 0 |
| 7 | 678,062 | 1 |
| 8 | 306,510 | 1 |
| 9 | 675,592 | 1 |
The final column is cumulative. The only solution occurs at K = 7.
The reconstructed course
The knight visits 55 squares, making 54 moves. The diagram below shows the complete path, the score on every visited square, the tower squares, and the nine squares left unvisited. The small move labels count from the initial square as move 0.

Here are the checkpoint moves in chronological order. Coordinates use row 0 for the top row and column 0 for the left column.
| Move | Square | Score |
|---|---|---|
| 3 | (5, 6) | 1 |
| 6 | (4, 4) | 16 |
| 9 | (2, 3) | 23 |
| 12 | (3, 0) | 528 |
| 15 | (0, 5) | 37 |
| 18 | (5, 3) | 88 |
| 25 | (2, 5) | 138 |
| 32 | (5, 5) | 272 |
| 39 | (4, 1) | 449 |
| 46 | (5, 1) | 750 |
| 53 | (0, 7) | 1,100 |
The first six checkpoints are three moves apart. The later ones are seven moves apart, confirming K = 7. The knight then makes one final move to visit the last tower.
The tower locations, named by the regions in the board transcription, are:
| Region | Tower square |
|---|---|
| A | (0, 3) |
| B | (2, 7) |
| C | (1, 1) |
| D | (2, 4) |
| E | (1, 5) |
| F | (4, 2) |
| G | (3, 0) |
| H | (3, 4) |
| I | (5, 4) |
| J | (5, 7) |
| K | (7, 0) |
| L | (6, 2) |
| M | (7, 4) |
The start is the tower for region K. The final tower is region B at (2, 7), reached by the upward move from (0, 7). That is why the journey ends with the conspicuous score 59,400.
For another view of the same solution, I plotted the altitude explicitly. The tower cubes make it easier to see why the straight two-square moves multiply or divide the score while ordinary knight moves only add to it.

The unvisited squares
The path visits 55 of the 64 board squares, leaving nine. For each unvisited square, I summed the scores on orthogonally adjacent squares that the knight did visit:
| Unvisited square | Neighbor sum |
|---|---|
(0, 0) | 44 |
(0, 1) | 574 |
(0, 6) | 1,436 |
(2, 1) | 2,012 |
(3, 6) | 1,646 |
(4, 6) | 1,890 |
(6, 7) | 9,925 |
(7, 3) | 8,392 |
(7, 5) | 7,690 |
Adding those nine neighbor sums gives
44 + 574 + 1,436 + 2,012 + 1,646 + 1,890
+ 9,925 + 8,392 + 7,690 = 33,609
I checked the result with a separate verifier rather than relying only on the search program. The verifier re-derived the altitude at every square from the tower set, checked the raw 3D move rule, recomputed every score and checkpoint, confirmed that each region contributed exactly one tower, and then calculated the neighbor sums independently.
What I liked about this puzzle was that the geometry and arithmetic kept constraining each other. The tower locations are unknown at the start, but a path at the wrong altitude immediately creates a contradiction somewhere else: a division stops being exact, a clue is reached at the wrong time, or a region loses its last possible tower square. Once the search found K = 7, the final course felt less like a black-box output and more like a chain of small, checkable deductions.
Final answer: 33,609
Appendix A: The complete solver
Attached below is a cleaned up version of the code I used to solve the puzzle. It searches all six possible write intervals and both possible starting heights, then prints the complete path, tower locations, and final neighbor-sum answer.
The complete solver
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <string>
#include <vector>
using namespace std;
using u64 = uint64_t;
using i128 = __int128;
constexpr int SIDE = 8;
constexpr int CELL_COUNT = SIDE * SIDE;
constexpr int REGION_COUNT = 13;
constexpr int CLUE_COUNT = 11;
constexpr int MAX_MOVES = CELL_COUNT - 1;
constexpr int START = 7 * SIDE;
constexpr int ALL_CLUES = (1 << CLUE_COUNT) - 1;
static constexpr const char* REGION_MAP[SIDE] = {
"AAAAABBB", "CCCDDEEB", "CFCDDDEB", "GFFHHIEE",
"GGFHHIIJ", "GKFLIIMJ", "GKLLLMMJ", "KKKLMMJJ",
};
struct Clue {
int cell;
long long value;
};
static const vector<Clue> CLUES = {
{0 * SIDE + 5, 37}, {0 * SIDE + 7, 1100},
{2 * SIDE + 3, 23}, {2 * SIDE + 5, 138},
{3 * SIDE + 0, 528}, {4 * SIDE + 1, 449},
{4 * SIDE + 4, 16}, {5 * SIDE + 1, 750},
{5 * SIDE + 3, 88}, {5 * SIDE + 5, 272},
{5 * SIDE + 6, 1},
};
static constexpr int KNIGHT_MOVES[8][2] = {
{1, 2}, {1, -2}, {-1, 2}, {-1, -2},
{2, 1}, {2, -1}, {-2, 1}, {-2, -1},
};
static constexpr int LEVEL_MOVES[4][2] = {
{0, 2}, {0, -2}, {2, 0}, {-2, 0},
};
int regionOf[CELL_COUNT];
u64 regionMask[REGION_COUNT];
int clueAt[CELL_COUNT];
int distanceLowerBound[CELL_COUNT][CELL_COUNT];
int interval, lastCheckpoint, maxMove;
int checkpointAt[MAX_MOVES + 1];
int pathCells[MAX_MOVES + 1];
int pathHeights[MAX_MOVES + 1];
i128 pathScores[MAX_MOVES + 1];
u64 visited;
int towerAt[REGION_COUNT];
int towersLeft, usedClues;
long long nodes;
struct Solution {
int interval, moves;
vector<int> cells, heights;
vector<i128> scores;
array<int, REGION_COUNT> towers;
};
vector<Solution> solutions;
static bool inside(int row, int column) {
return 0 <= row && row < SIDE && 0 <= column && column < SIDE;
}
static string scoreString(i128 value) {
if (value == 0) return "0";
bool negative = value < 0;
if (negative) value = -value;
string result;
while (value > 0) {
result += char('0' + int(value % 10));
value /= 10;
}
if (negative) result += '-';
return string(result.rbegin(), result.rend());
}
static long long largestRemainingClue() {
long long largest = 0;
for (int i = 0; i < CLUE_COUNT; ++i) {
if (!(usedClues & (1 << i)))
largest = max(largest, CLUES[i].value);
}
return largest;
}
static void recordSolution(int moves) {
Solution solution;
solution.interval = interval;
solution.moves = moves;
for (int i = 0; i <= moves; ++i) {
solution.cells.push_back(pathCells[i]);
solution.heights.push_back(pathHeights[i]);
solution.scores.push_back(pathScores[i]);
}
for (int region = 0; region < REGION_COUNT; ++region)
solution.towers[region] = towerAt[region];
solutions.push_back(solution);
}
static void dfs(int move, int cell, int height, i128 score) {
++nodes;
if (towersLeft == 0) {
if (move >= lastCheckpoint && usedClues == ALL_CLUES)
recordSolution(move);
return;
}
if (move >= maxMove || towersLeft > maxMove - move) return;
int nextCheckpoint = -1;
for (int next = move + 1; next <= maxMove; ++next) {
if (checkpointAt[next] >= 0) {
nextCheckpoint = next;
break;
}
}
if (nextCheckpoint >= 0) {
bool reachable = false;
for (int i = 0; i < CLUE_COUNT; ++i) {
if (!(usedClues & (1 << i)) &&
distanceLowerBound[cell][CLUES[i].cell] <= nextCheckpoint - move) {
reachable = true;
break;
}
}
if (!reachable) return;
i128 product = 1;
for (int next = move + 1; next <= nextCheckpoint; ++next)
product *= next;
if (score > i128(largestRemainingClue()) * product) return;
} else if (usedClues != ALL_CLUES) {
return;
}
int nextMove = move + 1;
int row = cell / SIDE;
int column = cell % SIDE;
int checkpoint = checkpointAt[nextMove];
auto tryMove = [&](int next, int nextHeight, i128 nextScore) {
int clue = clueAt[next];
if (checkpoint >= 0) {
if (clue < 0 || (usedClues & (1 << clue)) ||
i128(CLUES[clue].value) != nextScore) {
return;
}
} else if (clue >= 0) {
return;
}
int region = regionOf[next];
u64 nextVisited = visited | (1ULL << next);
bool claimedTower = false;
if (nextHeight == 2) {
if (towerAt[region] >= 0) return;
towerAt[region] = next;
--towersLeft;
claimedTower = true;
} else if (towerAt[region] < 0 &&
!(regionMask[region] & ~nextVisited)) {
return;
}
u64 previousVisited = visited;
int previousClues = usedClues;
visited = nextVisited;
if (checkpoint >= 0) usedClues |= 1 << clue;
pathCells[nextMove] = next;
pathHeights[nextMove] = nextHeight;
pathScores[nextMove] = nextScore;
dfs(nextMove, next, nextHeight, nextScore);
visited = previousVisited;
usedClues = previousClues;
if (claimedTower) {
towerAt[region] = -1;
++towersLeft;
}
};
for (const auto& delta : KNIGHT_MOVES) {
int nextRow = row + delta[0];
int nextColumn = column + delta[1];
if (!inside(nextRow, nextColumn)) continue;
int next = nextRow * SIDE + nextColumn;
if (visited & (1ULL << next)) continue;
tryMove(next, height, score + nextMove);
}
for (const auto& delta : LEVEL_MOVES) {
int nextRow = row + delta[0];
int nextColumn = column + delta[1];
if (!inside(nextRow, nextColumn)) continue;
int next = nextRow * SIDE + nextColumn;
if (visited & (1ULL << next)) continue;
if (height == 1) {
tryMove(next, 2, score * nextMove);
} else if (score % nextMove == 0) {
tryMove(next, 1, score / nextMove);
}
}
}
static void buildBoardData() {
for (int row = 0; row < SIDE; ++row) {
for (int column = 0; column < SIDE; ++column) {
int cell = row * SIDE + column;
int region = REGION_MAP[row][column] - 'A';
regionOf[cell] = region;
regionMask[region] |= 1ULL << cell;
}
}
fill(clueAt, clueAt + CELL_COUNT, -1);
for (int i = 0; i < CLUE_COUNT; ++i)
clueAt[CLUES[i].cell] = i;
}
static void buildDistanceTable() {
for (int start = 0; start < CELL_COUNT; ++start) {
fill(distanceLowerBound[start],
distanceLowerBound[start] + CELL_COUNT, 99);
vector<int> queue{start};
distanceLowerBound[start][start] = 0;
for (size_t head = 0; head < queue.size(); ++head) {
int cell = queue[head];
int row = cell / SIDE;
int column = cell % SIDE;
auto relax = [&](int nextRow, int nextColumn) {
if (!inside(nextRow, nextColumn)) return;
int next = nextRow * SIDE + nextColumn;
if (distanceLowerBound[start][next] >
distanceLowerBound[start][cell] + 1) {
distanceLowerBound[start][next] =
distanceLowerBound[start][cell] + 1;
queue.push_back(next);
}
};
for (const auto& delta : KNIGHT_MOVES)
relax(row + delta[0], column + delta[1]);
for (const auto& delta : LEVEL_MOVES)
relax(row + delta[0], column + delta[1]);
}
}
}
static void printAnswers() {
printf("\ntotal solutions: %zu\n", solutions.size());
for (const auto& solution : solutions) {
printf("\n=== K=%d, %d moves ===\n",
solution.interval, solution.moves);
for (int move = 0; move <= solution.moves; ++move) {
bool checkpoint = move > 0 &&
((move <= 18 && move % 3 == 0) ||
(move > 18 && (move - 18) % solution.interval == 0));
printf(" move %2d: (%d,%d) h=%d score=%s%s\n",
move, solution.cells[move] / SIDE,
solution.cells[move] % SIDE, solution.heights[move],
scoreString(solution.scores[move]).c_str(),
checkpoint ? " <== checkpoint" : "");
}
printf(" towers:");
for (int region = 0; region < REGION_COUNT; ++region)
printf(" %c=(%d,%d)", 'A' + region,
solution.towers[region] / SIDE,
solution.towers[region] % SIDE);
printf("\n");
i128 cellScores[CELL_COUNT]{};
bool visitedCells[CELL_COUNT]{};
for (int move = 0; move <= solution.moves; ++move) {
int cell = solution.cells[move];
visitedCells[cell] = true;
cellScores[cell] = solution.scores[move];
}
i128 answer = 0;
static constexpr int ORTHOGONAL[4][2] = {
{0, 1}, {0, -1}, {1, 0}, {-1, 0},
};
for (int row = 0; row < SIDE; ++row) {
for (int column = 0; column < SIDE; ++column) {
if (visitedCells[row * SIDE + column]) continue;
for (const auto& delta : ORTHOGONAL) {
int nextRow = row + delta[0];
int nextColumn = column + delta[1];
if (inside(nextRow, nextColumn) &&
visitedCells[nextRow * SIDE + nextColumn]) {
answer += cellScores[nextRow * SIDE + nextColumn];
}
}
}
}
printf(" ANSWER (sum of neighbor sums): %s\n",
scoreString(answer).c_str());
}
}
int main() {
buildBoardData();
buildDistanceTable();
for (interval = 4; interval <= 9; ++interval) {
lastCheckpoint = 18 + 5 * interval;
maxMove = min(MAX_MOVES, 18 + 6 * interval - 1);
if (lastCheckpoint > MAX_MOVES) break;
fill(checkpointAt, checkpointAt + MAX_MOVES + 1, -1);
for (int i = 1; i <= 6; ++i) checkpointAt[3 * i] = i - 1;
for (int i = 1; i <= 5; ++i)
checkpointAt[18 + interval * i] = 5 + i;
nodes = 0;
for (int startHeight = 1; startHeight <= 2; ++startHeight) {
visited = 1ULL << START;
fill(towerAt, towerAt + REGION_COUNT, -1);
towersLeft = REGION_COUNT;
usedClues = 0;
if (startHeight == 2) {
towerAt[regionOf[START]] = START;
--towersLeft;
}
pathCells[0] = START;
pathHeights[0] = startHeight;
pathScores[0] = 0;
dfs(0, START, startHeight, 0);
}
printf("K=%d: nodes=%lld, solutions so far=%zu\n",
interval, nodes, solutions.size());
}
printAnswers();
}