# puzzles · Sudoku by pure logic

**Lars Rocha's Larsdoku and path notation** · `sitoolz/puzzles.py` (the door to `larsdoku` and `wsrf_spn`) ↔ `js/puzzles.js` · fixture `fixtures/puzzles.json`

## What it is

Larsdoku solves Sudoku by pure logic, every step proven, zero guessing: 1465 of 1465 on Top1465,
a WSRF bitwise engine with GF(2) linear algebra behind it (`solve(puzzle)`, numba, JIT-warm on
first use). The WSRF Sudoku Path Notation is a PGN for a solve's reasoning (`spn.parse`,
`spn.write`, `spn.import_81`). The door adds what a page needs without a server, the same in both
languages: `norm`, `is_solved`, `fits(puzzle, solution)`, `candidates(grid, cell)`, `clues` and
`solve_singles`, the first two levels (naked and hidden singles) with the steps it took, and a
complete search solver for when a page needs any answer at all. The logic engine itself stays in
Python (numba, 5,000 lines); what crosses the line is its output, in the notation.

## Both signatures

| Python | JavaScript |
|---|---|
| `norm(grid)`, `UNITS`, `PEERS`, `is_solved`, `fits`, `candidates`, `solve_singles(grid)` → `{board, steps, solved}`, `clues` | `norm`, `units`, `peers`, `isSolved`, `fits`, `candidates`, `solveSingles`, `clues` |
| `solve_backtrack(grid)` (complete, by search, no logic) | `solveFull` (the same order, the same grid) |
| `rivals(grid, cell)` → `[[digit, rivals, in_row, in_col, in_box], ...]`, fewest first | `rivals` |
| `solve(puzzle, ...)` (Larsdoku, proven steps), `forge(seed)` → `(puzzle, solution)`, `forge_unique(mask)`, `path(puzzle)` → notation text | verify its boards with `isSolved` and `fits`; read its paths with `js/spn.js` |
| `spn.parse`, `spn.write`, `spn.import_81` (`wsrf_spn`) | `SISpn.parse(text)` → `{metadata, puzzle, solution, moves}`, `write(path)` (byte for byte the Python's), `replay(path)`, `notation`, `annotation`, `techSummary` |

Grids are 81-character strings, `0` or `.` for an empty cell. The Sudoku towns and mesas in the
terrain engine are Larsdoku's solved grids read as plans.

## The hint

`hint(puzzle, board, path)` gives the next move for a board: a naked or hidden single when there is one, else the first
placement of Larsdoku's proven path whose cell is still empty on the board (`path` a solve record or notation text; without
it the puzzle is solved here). `SISpn.hint(path, board)` does the second half in a page from a parsed path. Asked for by
the Sudoku Town demo.

## Rivals

`rivals(grid, cell)` is SIRO's rival count (Lars Rocha's `lars_siro`: `rivals_in_group`, `LarsScorer.rank`): for each candidate of
an empty cell, how many other empty cells of its row, its column and its box also hold it, each unit counted on its own (a cell in
both the row and the box counts twice), summed over the three; the fewest rivals is the most constrained and stands first, ties keep
the digit order, and the confidence is the gap between the last total and the first. `[]` for a filled cell. A hint ranking a page can
show beside `hint`, honest about being a ranking and not a proof (the survey measured SIRO's oracles at 29 percent on these towns).
On the easy grid cell 2 ranks 1 (7 rivals: 2 in the row, 4 in the column, 1 in the box), then 4 (9), then 2 (12), as lars_siro's
card reads it. Fixture: ten empty cells of each of three towns and one filled cell, the Python's rankings cross-checked against
`LarsScorer.rank`, the JavaScript's identical.

## The fixture

Wander's eight Sudoku towns (puzzles and Larsdoku's solutions) and a ninth easy grid: the checks
on each, the singles solver's board and steps on each (one is solved by singles alone, the same
51 steps on both sides), candidate lists, and Larsdoku's own solve records for four of them, whose
boards the JavaScript verifies. Then the complete solver's grid on six of them, three puzzles from
Larsdoku's forge (seeds 1 to 3; minimal, one solution, which the JavaScript search reproduces), and
three solves written in the notation, which the JavaScript parses, replays to the solution, and
rewrites without a byte's difference (166 moves), and 31 rival rankings: 149 checks. Larsdoku's `crossHatch` is written as
`HiddenSingle`, the notation's name for it. Larsdoku's forge raises on every seed at the moment (`larsdoku/forge.py:409`, its solve
returning `None`; found by the lars_siro survey), so the writer keeps the previous fixture's forged puzzles and paths when the forge
fails rather than dropping them.

## Provenance

Larsdoku, its forge and the notation are Lars Rocha's (`larsdoku`, `larsdoku_zs`, `wsrf_spn`).

## Jigsaw: irregular regions

From Lars Rocha's Jigsaw Shape Academy plan (jigsaw.txt): a region map of 81 digits replaces the 3x3 boxes, and the same
solvers run over the jigsaw units (`sitoolz.jigsaw`, `js/jigsaw.js`; the names are also on `puzzles`). `region_map(seed)` makes
nine connected regions of nine from the standard boxes by seeded cell transfers that keep both regions connected; `full_grid`
fills a map digit by digit (each digit once per row, the column in a seeded order); `jigsaw_puzzle(seed, clues)` removes
clues in a seeded order while the puzzle keeps one solution within a node budget; `to_spn_jigsaw`/`from_spn_jigsaw` carry the
map in the notation as `[Variant "Jigsaw"]` and `[RegionMap "..."]` (the parser keeps unknown header tags, so nothing in
`wsrf_spn` had to change); `jigsaw_districts` gives a town's quarters as the regions with the borders between them. Fixture
`fixtures/jigsaw.json`: six maps, their units and districts, three puzzles generated, solved and written identically.

