Fillomino in the browser with three rule sets inside. It is normally
stated in three clauses: cut the grid into polyomino regions; a region of
N cells is written N everywhere; and two regions of the same size may not
share an edge. That third clause is where this build starts. Read a filled
grid the only way a grid of numbers can be read — a region is a maximal
connected run of equal numbers — and the third clause becomes impossible to
break: two touching regions of size two are, on the page, one run of four 2s,
and 4 ≠ 2, which clause 2 already rejects. Fillomino has one rule.
Puzzle #28 in the solver-included series.
🌐 Live demo: https://sen.ltd/portfolio/fillomino/
📦 GitHub: https://github.com/sen-ltd/fillomino
The rules, in one sentence
Every connected run of equal numbers is exactly that many cells.
A run of 3s is three cells. A run of 5s is five. That is all. The engine checks
that one sentence and nothing else — it never asks whether same-size regions
touch.
So what is the third rule doing?
Saying "rule 3 is redundant" is only half right. Precisely:
- Formulate the puzzle over pictures (grids of numbers) and clause 3 is not expressible as a separate constraint. It is already implied.
- Formulate it over partitions (a cut into regions, plus labels) and clause 3 is a real constraint. Drop it and the solutions multiply.
So clause 3 does not narrow the answer. It guarantees the answer can be
written down. That is what makes Fillomino visually unique among the region
puzzles in this series — Heyawake, Norinori, LITS, Shikaku all have to print
their region walls, because numbers alone would not locate them. A Fillomino
prints nothing but numbers, and rule 3 is exactly the condition under which the
picture gives the partition back.
The demo makes the point literally: the borders are not in the data. As you
write numbers, a wall is drawn wherever two adjacent cells disagree, and the
region map appears out of nowhere as the grid fills.
Counting the theorem
If the claim is true, two independent enumerators must agree on every board.
-
countByRegionswalks regions: take the first blank cell, try every polyomino that could be its region, and check clause 3 out loud. -
countByCellswalks cells in reading order: write a number, then check only that the run through that cell has not outgrown its number and can still reach it. It has never heard of clause 3.
They share no code. On empty grids (npm run stats):
| grid | pictures (region walk) | pictures (cell walk) | partitions, rule 3 dropped |
|---|---|---|---|
| 1×1 | 1 | 1 | 1 |
| 2×2 | 5 | 5 | 12 |
| 3×3 | 445 | 445 | 1,434 |
| 4×4 | 259,728 | 259,728 | 1,691,690 |
The two middle columns agree exactly, on every board, which is the theorem.
The third column is the same region walk with clause 3 switched off: it counts
cuts rather than pictures, and by 4×4 it is counting each answer
6.51 times over. That factor is the ambiguity clause 3 removes.
On real boards the ablation is brutal. Of the sixteen 6×6 boards this repo
ships, 16/16 are unique as pictures — and only 2/16 are unique as
partitions once clause 3 is dropped. The median board admits 20 different
ways to cut it up; the worst admits 12,244. Every one of them writes the
same numbers.
The 2×2 case is small enough to check by hand: all-4s (one way), or a lone
corner 1 with an L-tromino of 3s (four ways). Five. Cutting it into two
dominoes of 2 dies as a picture — four 2s in a row, and 4 ≠ 2.
The variable is the cell; the domain is a set of sizes
Every other region puzzle hands you the regions and asks what goes inside.
Fillomino hands you nothing: the regions are the answer. So a cell's domain
is not "which colour" but "how big is the region I end up in", and every
propagator becomes a statement about how a run can still grow.
local — never past the frontier
// a run that has reached its number is finished: nothing beside it may join
if (comp.length === v) {
for (const c of comp) for (const u of nb[c]) {
if (state[u] === UNKNOWN) dropValue(d, u, v);
}
continue;
}
// a run still short of its number must grow — one way out means that cell is in
const exits = new Set<number>();
for (const c of comp) for (const u of nb[c]) {
if (state[u] === UNKNOWN && hasValue(d, u, v)) exits.add(u);
}
if (exits.size === 0) return -1;
if (exits.size === 1) state[[...exits][0]] = v;
Look at the first half. "A finished run blocks its own number next door" is
clause 3 — derived from the one-sentence formulation without ever naming it.
region — the two rules that need the whole board
reach. A region of size v is v connected cells that can all hold v.
So a cell keeps v only while it sits inside a v-capable component of at
least v cells — and a component of exactly v cells that already
contains a v is the region, in full.
if (mem.length < v) {
for (const c of mem) { if (state[c] === v) return -1; dropValue(d, c, v); }
} else if (mem.length === v && mem.some((c) => state[c] === v)) {
for (const c of mem) if (state[c] === UNKNOWN) state[c] = v; // component = region
}
cut. For a run still short of its number, delete one cell and re-flood. If
what remains can no longer reach the number, no legal region avoids that cell,
so the cell is in it. Articulation reasoning — and the rule that feels closest
to how people actually play.
const room = reachable(comp, v, d, n);
if (room.length < v) return -1;
for (const x of room) {
if (reachable(comp, v, d, n, x).length < v) { state[x] = v; return 1; }
}
probe — singleton consistency
Assume a number in a cell, run the region rules, and if the board dies the
number is gone. Restricted to the frontier (cells that already touch something
known) as a budget — and a budget that prunes less can never cost you
soundness.
How far does each rule set reach? (measured without circularity)
Generate only boards a rule set can solve, then measure how often that rule set
solves them, and you will discover it solves 100%. So these boards are
generated with no solvability filter at all — clues are peeled while
brute force still says "unique", and only then is the rule set asked:
| size | local | region | probe | clues |
|---|---|---|---|---|
| 5×5 | 0% | 10% | 95% | 9.6 / 25 |
| 6×6 | 0% | 4% | 96% | 13.5 / 36 |
The local rules finish nothing. The region rules finish about one board in
ten. Probing — still no search — gets to 95%, and the last few percent need
real backtracking whatever you do. From 900 random positions, the region rules
decided more cells than the local rules in 818 of them, +6.4 cells on
average, and the local rules never once won.
Since every propagator is sound, a rule set that finishes a board with no
search is also a uniqueness certificate.
Generation: most regions are never mentioned
Generation is a randomised region packing — grow a region, refuse to touch a
same-size neighbour, backtrack when stuck — followed by peeling clues off the
solved grid one at a time for as long as a rule set still finishes the board.
What is left over is the surprising part: 9–14% of the regions in a shipped
board carry no clue at all. Nothing printed says they exist. Their size,
shape and position are forced entirely by their neighbours.
And they are the small ones: a silent region averages 1.3–1.8 cells against
2.7–2.8 for regions in general. Roughly half of all lone 1s are never
printed — fenced in by bigger runs on all four sides, a 1 often has nowhere
else to be.
Per cell, that is the entire economics of a Fillomino clue. The share of cells
that end up clued falls steadily with region size — on the shipped 6×6 bank,
1:0.51 2:0.50 3:0.35 4:0.36 5:0.28, and on 8×8, 1:0.80 2:0.46 3:0.40. One clue buys more grid the bigger the region it lands in,
4:0.29 5:0.31
which is why a board covered in 1s and 2s is a board covered in clues.
One invariant falls straight out of the one rule and is checked on every board:
the grid holds a whole number of size-v regions, so the count of cells
labelled v is always a multiple of *v*.
The cap is a promise, not a crutch
These boards declare "no region is bigger than five cells", which keeps a
cell's domain at five values instead of a hundred and is what makes probing
affordable in a tab. It buys speed, not solutions: re-count the shipped 6×6
boards with the cap lifted to the whole grid and all 16 are still unique.
At 8×8 the same re-count re-proves 12 of 16, and the remaining 4 exhaust the
node budget — reported as budget exhaustion rather than quietly counted as
passes.
And how much is probing actually worth, in clues? Peel the same twenty solved
6×6 grids twice: stop while the region rules still finish and you leave 15.1
clues on average; let probe carry the board and you get to 13.8 (min 12,
max 17). The whole difficulty ladder is about 1.3 clues wide.
Soundness
Two enumerators that share no code with the propagators — one walking regions,
one walking cells — plus a validator that re-reads every region from scratch.
All of them must agree on solution counts across randomly generated boards; a
disagreement is how an unsound propagator gets caught. 72 tests.
Takeaways
- Fillomino's third clause is not a constraint on the answer — it is the guarantee that the answer can be printed. Over pictures, the puzzle is one sentence.
- Verified by two independent exhaustive enumerators (259,728 legal 4×4 boards, counted both ways). Drop the clause and the same answer is counted 6.51 times over; on real boards uniqueness collapses from 16/16 to 2/16.
- The variable is the cell and the domain is a set of region sizes, so every propagator is about growth: finished runs block, unreachable numbers die, articulation cells are forced.
- Rule-set reach measured on boards nobody filtered for solvability: local 0%, region 4–10%, probe 95–96%.
- After peeling, 9–14% of regions carry no clue at all — their existence is forced by their neighbours.
🌐 Live demo: https://sen.ltd/portfolio/fillomino/
📦 GitHub: https://github.com/sen-ltd/fillomino

Top comments (0)