Skip to content

What the Heck: Research Tools & Ideas

Larsdoku-ZS ships with tools for puzzle construction, uniqueness analysis, and forge-based generation. For oracle-guided research solving (Super Sus), see the companion package larsdoku which has larsdoku-research.

Which package has what?

Feature larsdoku-zs larsdoku
Board class yes no
Uniqueness checking yes yes
Forge variant yes yes
Constellation Forge yes yes
Mask Forge yes no
Super Sus (oracle-guided) no yes
larsdoku-research CLI no yes

Uniqueness Checking

Test whether a puzzle has exactly one solution or multiple.

larsdoku --unique 980700000600050000004008300700000040002004003000500020060000001008001030000090408
  Puzzle: 98070000060005000000...
  Clues: 23
  Unique: Yes -- one solution
from larsdoku_zs.engine import has_unique_solution

has_unique_solution("980700000600050000004008300700000040002004003000500020060000001008001030000090408")
# True

has_unique_solution("980700000600050000004008300700000040002004003000500020060000001008001030000090400")
# False

The One-Clue Cliff

Remove a single clue from a minimal puzzle and it shatters into multiple solutions:

# Unique (23 clues)
larsdoku --unique 980700000600050000004008300700000040002004003000500020060000001008001030000090408

# Remove R9C9 → multiple solutions (22 clues)
larsdoku --unique 980700000600050000004008300700000040002004003000500020060000001008001030000090400
from larsdoku_zs.engine import has_unique_solution

original = "980700000600050000004008300700000040002004003000500020060000001008001030000090408"
removed  = "980700000600050000004008300700000040002004003000500020060000001008001030000090400"

print(has_unique_solution(original))  # True  (23 clues)
print(has_unique_solution(removed))   # False (22 clues — R9C9 removed)

One digit. That's the difference between a puzzle with exactly one answer and a puzzle with many. In a minimal puzzle, every given is load-bearing.


Forge Variant

Turn a non-unique (multi-solution) puzzle into unique ones by reshuffling digits while keeping the same clue pattern:

larsdoku --forge-solve 910700000200050000003001400700000030008003004000500080020000006001006040000090300
  Forging seed puzzle...
  Seed forged in 107 checks (55ms)

    #  Puzzle                                             Status  Steps   Time
    1  450600000700030000001002800600000040002001008...  SOLVED     59    71ms
    2  590300000600080000004007100300000050007004001...  SOLVED     59   309ms
    3  710400000500060000008003200400000070003008002...  SOLVED     59    65ms
from larsdoku_zs import forge_variant

puzzles = forge_variant(
    "910700000200050000003001400700000030008003004000500080020000006001006040000090300",
    max_retries=20,
)

Same mask, different unique puzzles. Some need only basic techniques, others pull in ALS-XZ -- same skeleton, different difficulty. Useful for generating puzzle sets or testing how much variety a single clue pattern can produce.


Constellation Forge

Generate unique puzzles from any unique puzzle by relabeling digits 1-9. Every permutation of the digit mapping produces a structurally identical puzzle with different numbers.

# Generate 10 unique variants
larsdoku --forge-permute "000060010000300007000001300..." --forge-permute-count 10
from larsdoku_zs import Board

variants = Board.forge_permute(
    "000060010000300007000001300006040000300800020020003600004000003070002000900010050",
    count=10,
)
for v in variants:
    print(v)

Key finding: For well-formed masks (23-24 clues), 100% of digit permutations produce unique puzzles. That's 362,880 unique puzzles from a single source puzzle.


Mask Forge

Generate a unique puzzle from a bare mask (which positions have clues):

from larsdoku_zs.mask_forge import forge_unique

# 'X' = clue position, '0' = empty
mask = "X00X0000000X00X000X000000X0000X00000X000X0000X00000X000X000X000X00000X0000X000X0"

puzzle, solution, checks, elapsed = forge_unique(mask, max_seconds=60, verbose=True)
print(f"Puzzle: {puzzle}")
print(f"Found in {checks} checks ({elapsed:.1f}s)")

The forge fills a random solved grid, maps it onto the mask, and verifies uniqueness -- repeating until it finds one that works.


Board Class: Solve + Analyze

The Board class wraps all of the above into a single object:

from larsdoku_zs import Board

b = Board("530070000600195000098000060800060003400803001700020006060000280000419005000080079")

# Full solve
result = b.solve()
print(result['success'])
print(result['technique_counts'])

# Single technique detection
det = b.detect("nakedPair")
print(det.found, det.eliminations)

# GF(2) linear algebra
gf2_result = b.gf2()

# SIRO prediction
b.compute_zones()
pred = b.siro_predict()

Super Sus (larsdoku only)

Oracle-guided solving lives in the larsdoku package. If you want to explore what techniques can do when steered by a known solution:

pip install larsdoku
larsdoku-research \
  --trust-solve-to 179348652536721948284569371912437586358916724467285193723194865845673219691852437 \
  --detail --board \
  000000000500020008000000000000000000300010004000000000000000000800070009000000000
from larsdoku.research import super_sus_solve

result = super_sus_solve(
    "000000000500020008000000000000000000300010004000000000000000000800070009000000000",
    solution="179348652536721948284569371912437586358916724467285193723194865845673219691852437",
    detail=True,
)
print(f"Steps: {result['n_steps']}, Oracle saves: {result['oracle_saves']}")
# Steps: 100, Oracle saves: 0

8 clues, 72 placements, zero oracle saves. Every FPC-Elim that fired was already pointing at the right answer. The techniques are the engine, the oracle is the steering wheel.

See the larsdoku Research Tool docs for the full deep-dive.