Guides
Troubleshooting
Fix null generation, meta constraint errors, and common integration mistakes.
Troubleshooting
Symptoms users hit in real apps, and how to fix them. Pair with Guides and API.
Generation returns null
Symptom: generatePuzzle(...) is null.
Cause: Carve/fill retries exhausted for that difficulty and variant profile.
Fix:
- Retry the call (non-seeded paths are nondeterministic).
- Try an easier difficulty or
{ maxAttempts: higher }. - For packs that must never fail, use
BatchEngine.generateOne— it retries aggressively and throws only if still impossible.
const game = engine.generatePuzzle("expert", { maxAttempts: 80 });Solved puzzle ignores cages / thermos / dots
Symptom: Killer (or Thermo / Kropki) solve returns a classic-valid grid that breaks the published cages.
Cause: You called methods on the bare engine without withMeta.
Fix:
const game = killer.generatePuzzle("easy")!;
const scoped = killer.withMeta(game.metaConstraints!);
scoped.solve(game.puzzle); // correctPersist metaConstraints next to the digit grid in saves/APIs.
Top-level solve / validateBoard “works” but rules feel wrong
Symptom: 6×6, diagonal, Samurai, etc. look broken or too permissive.
Cause: Top-level exports are classic 9×9 only.
Fix: Use one PuzzleEngine for the whole session:
const engine = createEngine({ variant: "6x6" });
engine.solve(board);
engine.validateBoard(board);applyMove fails with a reason
reason | Meaning | What to do |
|---|---|---|
"That cell is a given." | You passed puzzle and the cell is a clue | Disallow edits on givens in UI |
"Value must be 1–9 or null." (or 1–6) | Digit not in variant digit set | Clamp keypad to engine.variant.grid.digits |
"Digit conflicts with a house…" | Breaks uniqueness in a house | Show conflict highlight; optional soft-allow in UI by skipping isValidMove |
Out-of-bounds coordinates throw SudokuEngineError (they are programmer errors, not player mistakes).
SudokuEngineError: Row/column out of range
Cause: Indices outside 0 … size-1 (classic/Samurai differ: 9 vs 21).
Fix: Guard with engine.isInBounds(row, column) before calling play/hint APIs, or catch SudokuEngineError.
Samurai board full of nulls after “solve”
Symptom: Large empty regions remain null on a solved Samurai board.
Cause: Those cells are inactive (not in the five 9×9 blocks). They must stay empty.
Fix: Only render / score playable cells:
import { isPlayableCell } from "@reetesh/sudoku-engine";
if (!isPlayableCell(engine.variant, row, col)) {
// skip cell in UI
}isBoardComplete already ignores inactive cells.
Import string returns null
Cause: Wrong length (must be size² characters) or unexpected charset for stringToBoard / puzzleFromString.
Fix: Classic: 81 chars. 6×6: 36 chars. Samurai: 441 chars (inactive as .). Prefer engine.puzzleFromString for the active variant.
validateImportedPuzzle says not unique / not solvable
| Field | Meaning |
|---|---|
valid: false | Shape or house conflict on the given board |
solvable: false | No solution under this variant |
unique: false | More than one solution — not a fair puzzle |
Do not ship non-unique imports as “daily” content without warning players.
Batch distribution throws
Symptom: SudokuEngineError: Counts must add up to N.
Cause: distribution values do not sum to count, or a count is negative/non-integer.
Fix:
engine.generateBatch({
count: 10,
distribution: { easy: 10, medium: 0, hard: 0, expert: 0 },
});Also check MAX_BATCH_SIZE if the count is huge.
Unknown variant / wrong id
Cause: Typo or custom id not registered.
Fix: Use typed VariantIds from listVariants(), or pass createEngine({ definition }) for custom rules. getVariant("…") throws SudokuEngineError for unknown names.
Windoku vs Hyper look the same in my UI
They are different window placements. Use getVariant("hyper") / getVariant("windoku") and draw the correct overlays — see Overview.
Difficulty feels “off”
Clue-count bands (rateDifficulty) and technique heuristics (rateDifficultyByTechniques) are approximate. Generated difficulty is the requested band, not a human-grade proof of technique set. For UI labels, trust the requested difficulty unless you build your own rater.
Still stuck?
- Confirm package version (
3.0.0+) and Migration. - Reproduce with
createEngineonly (no mixed classic helpers). - Open an issue with: variant id, difficulty, whether
metaConstraintswere attached, and a minimal board string.