Biscuit Lab

Build log

KSudoku exposes two difficulty knobs — one of them does nothing

Building Killer Sudoku generation, I reached for KSudoku's cage difficulty parameters and found one of the two was dead code — which is why Puzzle Lab grades by technique instead of by a knob.

When I started on Killer Sudoku for Puzzle Lab, I did the sensible thing and read the reference implementation first. KDE's KSudoku is the best-documented open-source generator for this exact pipeline: generate a solved grid, grow connected cages with a randomized flood-fill, then verify a unique solution with Dancing Links. If I was going to build a cage generator, I wanted to see how a mature one exposed difficulty.

Its cage generator header offers two knobs: maxSize, the largest number of cells a cage can have, and maxValue, the largest allowed cage total. Two dials for difficulty. Reasonable — bigger cages mean fewer combination constraints, and capping the sum should shape the puzzle too.

So I wired up both and started turning them.

One of the dials wasn't connected

maxSize did what it said. maxValue did nothing. I'd change it, regenerate, and the output distribution wouldn't move at all. When I stopped trusting the interface and read the generator body, there it was: maxValue is accepted and then never actually used. It's a knob bolted to the dashboard with no cable behind it.

This is the trap with reading an API instead of an implementation. A parameter that exists, has a sensible name, and compiles looks exactly like a parameter that works. The only way to tell them apart is to follow the value into the code and check that something reads it.

Difficulty was never going to be one dial anyway

The deeper lesson is that cage difficulty doesn't live in one or two scalars. The levers that actually move it are structural:

  • how many single-cell cages there are (they're givens in disguise — many is easy, zero is hard),
  • the average cage size,
  • how many cages have only one valid combination (a 3-in-2 is always {1,2} — a free foothold), and
  • whether cages line up with rows, columns, and boxes or deliberately cross them (alignment creates easy Innies/Outies).

No single maxValue captures that. Which is the whole reason Puzzle Lab doesn't grade with generator parameters at all: it grades by the hardest technique its HumanSolver was forced to use to reach the unique solution. The difficulty label isn't a dial I set — it's a description of the reasoning the finished puzzle actually demands.

What I took from it

Someone who has built one of these put it well: the solver is a couple of weeks of work, and the grader — the thing that emulates human technique to rate a board — is the better part of a year. Difficulty is the hard part, and it resists being compressed into a knob.

So: read the implementation, not the interface, before you build a calibration on top of it. An exposed parameter is a claim, not a guarantee. I'd rather spend an hour confirming a value is actually read than a day tuning a dial that was never wired in.