feat: complete advanced Sudoku workbench

This commit is contained in:
2026-08-31 08:20:30 +02:00
parent 8ca9300ab3
commit 0a1bdc1a8c
99 changed files with 20793 additions and 923 deletions
+60 -7
View File
@@ -4,6 +4,30 @@ All notable changes are documented here.
## Unreleased
## 0.2.0 - 2026-08-31
- Added staged guided hints that reveal focus, technique, reasoning and effects
before an explicit, undoable apply action.
- Added legal centre-candidate filling, invalid note pruning and optional peer
note maintenance after placements; erasing a value never invents candidates.
- Added Jellyfish, finned X-Wing/Swordfish, Skyscraper, Two-String Kite, Simple
Colouring, W-Wing, X-Chain, XY-Chain and alternating-inference-chain logical
steps with deterministic evidence.
- Added Unique Rectangle only behind a completed exact uniqueness proof; a
truncated search never enables uniqueness-dependent logic.
- Added quick and full setter-quality audits with ambiguity witnesses,
contradiction suspects, per-item critical/redundant/unknown findings, a cell
heatmap and optional bounded minimality proof.
- Added explicit per-check and aggregate quality budgets, cancellation and
serializable check evidence so a capped search remains unknown rather than
becoming a false proof.
- Added minimum, odd/even, disjoint-groups, little-killer and sandwich rules
across validation, candidate filtering, exact solving, rendering and setting.
- Added between, German-whisper, region-sum, modular, entropic and zipper lines;
clone and extra regions; double arrows; and row, column and box indexers.
- Added display-only Fog of War backed by a complete locally stored solution.
Only initial lights, givens and correctly entered values reveal cells; fog is
never treated as an extra solution constraint.
- Added optional digit-completion counts with muted complete digits and red
over-completion warnings.
- Added toggleable Ctrl/Command-click matching-digit highlights without turning
@@ -12,10 +36,16 @@ All notable changes are documented here.
remain accidentally active.
- Replaced ambiguous V/X pair labels on the board with numbered 5/10 badges and
gave inequalities a directional chevron with a marked lesser-value tip.
- Added thirteen uniquely checked built-in examples covering every supported
constraint family.
- Added bounded, seedable generation for classic and twelve variant families,
plus independent uniqueness and evidence-based difficulty assessment.
- Added fourteen uniquely checked built-in examples covering the original core
constraint families.
- Expanded bounded, seedable generation for classic and twelve variant families
with mixed-family recipes, sparse/balanced/dense clue controls and additional
reflection, diagonal and four-way clue symmetries.
- Added explicit minimal-givens mode with proof/unknown reporting, deterministic
batches of up to 12 candidates and rankings by difficulty or clue count.
- Added required/forbidden/minimum/maximum logical-technique profiles and an
exact hardest-technique target, accepted only against an independent complete
logical path and exact uniqueness check.
- Added X-sum, skyscraper, quadruple and maximum-cell constraints across the
model, validator, exact solver, renderer, setter and f-puzzles interchange.
- Added per-clue false semantics for liar/Wrogn puzzles, including bounded
@@ -25,14 +55,30 @@ All notable changes are documented here.
- Added outside-grid clue margins, combined dual X-sum/skyscraper labels,
outward maximum arrows and visually distinct false clues.
- Added strict, bounded local imports for SudokuPad/CTC data and supported
Penpa+ long links, including source reporting and explicit incompatibility
lists instead of silently discarded constructs.
Penpa+ long links, with a structured preview of mapped semantics, preserved
drawings, metadata and compatibility warnings.
- Added a bounded inert visual model for f-puzzles and SCL lines, polylines,
rectangles, ellipses, circles and text; source identity, scalar metadata and
drawings survive edits, explicit saves, autosaves, reopening and project/share
round-trips without becoming rules, and render beneath Fog on the board.
- Added SudokuPad/CTC JSON and self-contained `scl…` export with progress,
regions, cages and canonical constraint drawings, while rejecting negated
clues or free-form global rules it cannot preserve safely.
- Added a compatibility check which never fetches SudokuPad short IDs or other
server-hosted puzzle references.
- Added standalone SVG, high-resolution PNG and single-page PDF visual exports
for supported constraints, givens and optional solving progress.
for supported constraints, safe preserved drawings, givens and optional
solving progress.
- Added named savepoints, isolated hypothesis branches, keep/discard decisions
and read-only replay of complete solving states.
- Added debounced recovery snapshots for puzzle state, progress, aid-mémoire
and a validated, size-bounded gameplay history, with explicit restore/discard
handling after an interruption.
- Upgraded the local Library with title/tag search, tag and completion filters,
safe grid thumbnails, tag editing and selected export, duplication or
confirmed deletion.
- Added a relative-scope web app manifest and subpath-aware service worker with
an offline application-shell fallback after the first successful load.
- Added an optional configurable aid-mémoire whose labelled scratch cells
support values, both note styles and colours without constraining the puzzle;
its state is preserved in undo, replay, Library progress and project/share
@@ -46,6 +92,13 @@ All notable changes are documented here.
- Added technique-targeted practice generation which mines a bounded,
deterministic set of uniquely checked puzzles and verifies the requested
technique in the logical solve path.
- Added a persisted 75200% board viewport, Fit and board-scoped zoom shortcuts,
plus an announced Pan mode which pauses cell pointer targeting.
- Added tap-toggle multi-selection and a single safe-area-aware sticky entry pad
at narrow widths, with touch-sized zoom controls.
- Added eight hue-and-pattern colour marks, configurable off/concise/detailed
candidate narration, and fog-safe board, toolbar, helper and guided-hint
boundaries.
- Added real ARIA row/gridcell semantics, detailed cell descriptions and
non-wrapping Arrow, Home/End, Control/Command + Home/End and Page Up/Down
keyboard navigation for the puzzle and scratch grids.
+211 -44
View File
@@ -9,76 +9,243 @@ release also runs independently from any static HTTPS host or local preview.
- **Play** — keyboard, mouse and touch entry; multi-cell selection; values,
corner/centre notes and colours; undo/redo; named savepoints, hypothesis
branches and read-only replay; conflict highlighting; timer and local
progress; optional digit-completion counts, matching-digit highlights and a
configurable non-constraining aid-mémoire.
- **Set** — givens, metadata, regions and typed constraints; uniqueness checks
with overlap-safe cage replacement and selection-based cage removal.
- **Generate** — seedable, bounded construction for classic and 12 variant
families; independent uniqueness verification and evidence-based difficulty
assessment; deterministic mining for a requested logical technique.
branches and read-only replay; staged guided hints; candidate maintenance;
conflict highlighting; timer and local progress; optional digit-completion
counts, matching-digit highlights and a configurable non-constraining
aid-mémoire.
- **Set** — givens, metadata, regions and registry-backed constraints;
overlap-safe clue replacement and removal; quick uniqueness checks; and a
bounded quality lab for ambiguity, contradictions, redundancy and
minimality.
- **Generate** — seedable single or batch construction for classic and 12
variant families, including mixed-family recipes, clue symmetry, constraint
density, optional minimal-givens proof and independently checked technique
profiles.
- **Solve** — exact solution counting plus an original human-style engine whose
steps include structured evidence, placements and eliminations.
deterministic steps include structured evidence, placements and
eliminations. Uniqueness-dependent logic is disabled unless uniqueness has
already been proved by a completed exact search.
- **Helpers** — generalized sum combinations with positional candidates and
manual eliminations; selected-cell and house candidate-link analysis; Killer
combinations, 45-rule residuals, and Kropki, sum-pair or inequality pairs.
Classic grids and common variants share one bounded puzzle model: irregular
regions, diagonals, Killer cages, thermometers, arrows, Kropki and numbered
5/10 sum-pair clues, inequalities, renban lines, palindromes, X-sums,
skyscrapers, quadruples, maximum cells, anti-knight, anti-king and
non-consecutive rules. Every local clue can also be required to be false for
liar/Wrogn constructions. Fourteen original bundled examples demonstrate every
supported constraint and are exact-search verified as unique.
## Constraint coverage
Classic grids and variants share one validated puzzle model and constraint
registry. The production engine currently supports:
- classic, irregular and extra regions; diagonals; disjoint groups;
anti-knight, anti-king and non-consecutive rules;
- Killer cages, ordered clone regions, quadruples, maximum/minimum cells and
odd/even cells;
- thermometers, arrows, renban and palindrome lines, between lines, German
whispers, region-sum lines, modular lines, entropic lines, zipper lines and
double arrows;
- Kropki dots, numbered 5/10 sum pairs and inequalities;
- X-sums, skyscrapers, little-killer diagonals and sandwich sums; and
- row, column and rectangular-box indexers, plus display-only Fog of War.
Each supported semantic constraint is validated and participates in candidate
filtering and exact solving. Negatable clues can instead be required to be false
for liar/Wrogn constructions; global house rules, extra regions and fog are not
given a misleading false mode. Fourteen original bundled examples cover the
core families and the uniquely checked “Truth and lies” construction. The
newer constraint packs are covered by focused validation, feasibility and exact
solver tests rather than bundled third-party puzzles.
Fog is deliberately a presentation rule, not a hidden solver constraint. It
can be set only when the puzzle carries a complete solution that validates
against the current givens and constraints. Initial lights, givens and correctly
entered digits reveal a radius-zero or radius-one Chebyshev neighbourhood;
wrong entries reveal nothing. Obscured cells are disabled and their values,
notes, candidates, hints, quality overlays and fully hidden clue graphics are
omitted. The trusted solution remains part of the local puzzle document.
## Guided solving and candidate maintenance
A guided hint asks the worker for one supported logical step and discloses it in
four explicit stages: where to look, technique, reasoning and effects preview.
Later-stage answer content is not placed in the document before it is revealed,
and the board does not change until **Apply this step** is chosen. Applying a
step is one undoable transition.
Candidate controls can fill every empty cell with its currently legal centre
candidates, remove invalid centre/corner notes, and optionally prune peer notes
after a placement. Erasing a digit never invents candidates. If an
elimination-only hint is applied without tracked candidates, the app first
creates a complete legal centre-candidate grid and then applies the explicit
elimination.
The logical engine covers singles; naked and hidden pairs, triples and quads;
pointing and claiming; X-Wing, Swordfish and Jellyfish; finned X-Wing and
finned Swordfish; XY-Wing and XYZ-Wing; Skyscraper; Two-String Kite; Simple
Colouring; W-Wing; X-Chains, XY-Chains and alternating inference chains; and
Killer-cage deductions. Unique Rectangle is available only when the caller
supplies a completed uniqueness proof. The engine never infers uniqueness from
a puzzles appearance, metadata or a truncated search.
## Setter-quality analysis
The quick setter check performs a bounded zero/one/two-solution audit. A pair of
solutions produces a concrete ambiguity witness with every differing cell and
value. For a uniquely proved baseline, full analysis uses leave-one-out exact
searches to classify each given and constraint as critical, redundant or
unknown and creates a per-cell criticality heatmap. For an unsatisfiable
baseline it instead uses bounded deletion tests to localise a contradictory
core. Optional minimality is proved only when the unique baseline and every
required removal check complete.
Every quality search has configurable per-check node/time limits and shared
aggregate check/node/time limits. Results retain the checks performed, elapsed
time and reasons for unknown conclusions. Reaching a safety limit is never
silently converted into uniqueness, redundancy, contradiction localisation or
minimality. The worker analysis can be cancelled without discarding the last
completed result.
## Generator 2.0
Generation supports the existing classic, diagonal, anti-knight, anti-king,
non-consecutive, Killer, thermo, arrow, Kropki, XV, inequality, renban and
palindrome families. Compatible families can be combined when they share a
supported grid size. Local marking density can be sparse, balanced or dense;
for Killer this changes cage granularity, while inherently global rules retain
their fixed meaning.
Clue removal supports no symmetry, half-turn rotation, horizontal or vertical
reflection, either diagonal reflection, and four-way quarter-turn symmetry.
Minimal-givens mode checks every retained given individually and reports either
a completed proof or the exact unknown reasons. Individual minimisation may
break the requested visual symmetry, which is reported rather than hidden.
Technique profiles can require or forbid supported logical techniques, set
minimum/maximum occurrence counts and require an exact hardest technique. A
puzzle is accepted only when an independent, complete logical path matches the
whole profile and an independent exact check proves uniqueness. Deterministic
batches contain at most 12 candidates and can be ranked by difficulty, fewest
givens or most givens; failed bounded candidates remain visible as failures and
are never included in the verified ranking. Generation is cancellable by
terminating and restarting its worker.
Requested difficulty still controls the clue-removal target rather than
promising a rating. The displayed 0100 result is calculated afterwards from
logical techniques, clue load and reproducible exact-search evidence. Supported
size/family combinations are deliberately restricted, and a bounded mixed
construction may fail cleanly for an incompatible or unusually difficult
recipe.
## Mobile and accessibility
The board viewport offers a locally persisted 75200% scale in 25% steps,
increment/decrement controls and a Fit action that returns to 100% and the
origin. Control/Command + plus, minus or zero are board-scoped equivalents.
Explicit Pan mode announces its state, cancels any in-progress drag selection
and disables cell pointer targeting until it is stopped. Tap multi-select, also
available with <kbd>M</kbd>, toggles cells individually without drag selection
and never leaves the active selection empty.
At widths up to 48rem, Play and Set show exactly one sticky entry pad (outside
read-only replay) with safe-area padding and touch-sized zoom controls. Eight
colour marks use distinct patterns as well as hues and expose the hue/pattern
name on both the Sudoku board and aid-mémoire. Screen-reader candidate detail is
independently selectable as Off, concise counts or detailed digits without
changing the visible board.
The board exposes real row/gridcell semantics and concise per-cell state to
assistive technology, including candidates, notes, colours, conflicts and
touching variant clues. Arrow keys never wrap; Home/End, Control/Command +
Home/End and Page Up/Down provide row- and grid-level navigation. The same
navigation model is available in the aid-mémoire scratch grid.
assistive technology, including candidates, notes, colours, conflicts,
touching variant clues, hint previews and quality findings. Arrow keys never
wrap; Home/End, Control/Command + Home/End and Page Up/Down provide row- and
grid-level navigation. The same navigation model is available in the
aid-mémoire scratch grid. Guided-hint and quality status changes use live
regions.
Fogged cells remain disabled and expose only an obscured status to assistive
technology. Hidden values, notes, constraint descriptions, candidate/helper
overlays and hint effects are masked, and toolbar/helper selection is reduced
to visible cells. A guided hint is shown only if all of its focus and effect
cells are visible; otherwise the app returns a generic no-visible-hint status.
## Interoperability and visual export
The import dialog recognises compact grids, Sudoku Tools JSON/share fragments,
raw or inline f-puzzles data, SudokuPad/CTC (SCL) data, and Penpa+ self-contained
long links. It never follows a URL or resolves a server-side short ID. Imports
are size/decompression bounded, report their detected source before applying,
and stop with a list when a construct cannot be represented faithfully.
are size/decompression bounded. Before applying one, a structured mapping
preview separates solver-enforced semantics, preserved drawings, retained
metadata and warnings.
SudokuPad compatibility currently preserves cells, givens and progress, notes,
regions, metadata, solutions, Killer cages and supported global rules. Penpa+
compatibility is intentionally narrower: square, unrotated Sudoku grids with
givens/progress, thermometers and arrows. Visual-only lines, overlays, custom
symbols and other unsupported geometry are reported rather than ignored.
f-puzzles export likewise refuses false clues it cannot preserve.
The shared inert visual model supports bounded lines, polylines, rectangles,
ellipses, circles and text. f-puzzles maps its decorative line, rectangle,
circle and text fields; SCL maps supported lines, underlays, overlays and
arrows. Drawings, local source identity and scalar metadata survive domain
edits, explicit saves, autosaves, reopening, and project/share exports. They
never become Sudoku rules unless the preview also lists a mapped semantic
constraint. Raw SVG, HTML, CSS, paths or code, unsafe colour syntax, unknown
visual fields, excessive data and rotated or otherwise unrepresentable layouts
are rejected rather than executed or silently changed.
SudokuPad compatibility maps cells, givens and progress, notes, regions,
metadata, solutions, Killer cages and supported global rules. Export can create
readable SudokuPad/CTC JSON or a self-contained `scl…` payload with progress,
regions, cages and canonical drawings for registered constraints. Sudoku Tools
retains its bounded constraint record for its own exact round-trip; another SCL
consumer should only be assumed to enforce constructs it represents natively.
Negated clues and free-form global rules cannot be exported to SCL.
Penpa+ compatibility remains intentionally narrower: square, unrotated Sudoku
grids with givens/progress, thermometers and arrows. f-puzzles export likewise
refuses false clues and underlay, free-coordinate or offset drawings it cannot
preserve faithfully. Preserved source drawings appear on the interactive board
and in static/SCL exports; Fog covers their obscured in-grid portions.
Export can produce a standalone SVG, a high-resolution PNG, or a single-page
PDF entirely in the browser. Each visual uses the same trusted puzzle model and
renders region boundaries, supported variant clues and givens; current values,
notes and cell colours can be included or omitted with the progress switch.
renders region boundaries, supported variant clues, safe preserved drawings and
givens; current values, notes and cell colours can be included or omitted with
the progress switch.
Generation supports only size/variant combinations which pass the bounded
construction checks. The requested difficulty controls clue-removal targets;
the displayed 0100 result is calculated afterwards from logical techniques,
clue load and reproducible exact-search evidence. A timeout or node limit is
reported as unknown and never promoted to a uniqueness or difficulty claim.
Technique practice performs a bounded, deterministic search and returns a
puzzle only when its independently analysed solve path actually contains the
requested technique.
## Privacy and storage
## Privacy, recovery and Library
Puzzle parsing, solving, generation and export happen entirely in the browser.
There are no analytics, advertisements, accounts or network-backed puzzle
lookups. Projects and progress are saved only in local IndexedDB after an
explicit save action and can be cleared from inside the app.
Exported files and share URLs contain the puzzle data you chose to include.
lookups. Explicit projects are stored in IndexedDB. A separate, debounced
autosave slot retains the current puzzle state, progress, aid-mémoire and a
bounded gameplay history. After an interrupted session, the app offers to
restore or discard that recovery state without silently replacing an explicit
project. Its status distinguishes checking, saving, saved, ready and unavailable
states.
Gameplay history includes undo/redo moments, named savepoints and hypothesis
branches, and is validated when restored. It is bounded to 500 moments and one
MiB for its serialized history payload. The Library supports title and tag
search, tag and completion filters, script-free grid thumbnails, bounded tag
editing, and selected export, duplication or confirmed deletion. Whole-library
import and export remain versioned operations.
If IndexedDB is unavailable, a bounded in-memory fallback keeps work for the
current tab and the interface reports that persistence is unavailable. Browser
storage can still be removed by the browser or user, so important projects
should be exported. Exported files and share URLs contain the puzzle data you
chose to include.
SudokuPad short IDs identify server-hosted records and therefore cannot be
resolved by this deliberately offline application. Embedded f-puzzles payloads
and user-provided Sudoku Tools JSON documents are accepted without contacting
their originating site. Externally authored puzzle definitions are not bundled.
## Offline application shell
Production builds include a relative-scope web app manifest and register a
subpath-aware service worker after the page has loaded. It caches the application
shell and same-origin built assets, uses network-first navigation with a cached
fallback, serves cached static assets when available and removes obsolete cache
versions.
The first successful online load is still required to install those resources,
and service-worker availability depends on browser support and a secure origin.
Registration is progressive enhancement: the application continues to work
when a service worker is unavailable, and puzzle operations never depend on a
network connection.
## Development
Requirements: Node.js 22 or newer and npm 11 or newer.
@@ -101,7 +268,7 @@ npm run release:artifact
The command verifies the manifest, types, formatting, tests, production build,
browser workflows and Toolbox contract before creating a deterministic
`release/sudoku-tools-0.1.0.zip` with a matching SHA-256 sidecar.
`release/sudoku-tools-0.2.0.zip` with a matching SHA-256 sidecar.
## Scope and references
+2 -2
View File
@@ -1,8 +1,8 @@
# Corresponding source
The corresponding source for Sudoku Tools 0.1.0 is available at:
The corresponding source for Sudoku Tools 0.2.0 is available at:
https://git.add-ideas.de/lotobo/sudoku-tools/src/tag/v0.1.0
https://git.add-ideas.de/lotobo/sudoku-tools/src/tag/v0.2.0
The production archive is generated from that tag with the scripts and exact
dependency lock contained in the repository. It contains source identity,
+19
View File
@@ -0,0 +1,19 @@
{
"name": "Sudoku Tools",
"short_name": "Sudoku",
"description": "Set, play, solve and analyse Sudoku puzzles locally.",
"id": "./",
"start_url": "./",
"scope": "./",
"display": "standalone",
"background_color": "#f4f0e8",
"theme_color": "#272421",
"icons": [
{
"src": "./favicon.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any"
}
]
}
+119
View File
@@ -0,0 +1,119 @@
/* Sudoku Tools offline worker. This file intentionally has no dependencies. */
const CACHE_PREFIX = "sudoku-tools-shell-";
const CACHE_VERSION = "v1";
const CACHE_NAME = `${CACHE_PREFIX}${CACHE_VERSION}`;
const CORE_ASSETS = ["./", "./manifest.webmanifest", "./favicon.svg"];
const BUILD_ASSET_PATTERN =
/(?:(?:\.\/)?assets\/)?[A-Za-z0-9][A-Za-z0-9._-]*-[A-Za-z0-9_-]{6,}\.(?:css|js|json|png|svg|wasm)/gu;
async function cacheAssetTree(cache, assetUrl, discovered, depth = 0) {
const url = new URL(assetUrl, self.registration.scope).href;
if (discovered.has(url)) return;
discovered.add(url);
const response = await fetch(url, { cache: "no-cache" });
if (!response.ok) return;
await cache.put(url, response.clone());
const contentType = response.headers.get("content-type") ?? "";
if (depth >= 3 || !/(?:javascript|text\/css)/iu.test(contentType)) return;
const source = await response.text();
await Promise.all(
[...source.matchAll(BUILD_ASSET_PATTERN)].map((match) => {
const reference = match[0];
const base = reference.includes("assets/")
? self.registration.scope
: url;
return cacheAssetTree(
cache,
new URL(reference, base).href,
discovered,
depth + 1,
);
}),
);
}
async function cacheDocumentAssets(cache) {
const response = await fetch("./", { cache: "no-cache" });
if (!response.ok) throw new Error("Could not cache the application shell.");
await cache.put("./", response.clone());
const html = await response.text();
const urls = new Set(CORE_ASSETS.slice(1));
for (const match of html.matchAll(/(?:src|href)=["']([^"'#]+)["']/giu)) {
const raw = match[1];
if (raw === undefined) continue;
const resolved = new URL(raw, self.registration.scope);
if (resolved.origin === self.location.origin) urls.add(resolved.href);
}
const canonicalUrls = new Set([new URL("./", self.registration.scope).href]);
await Promise.all(
[...urls].map((url) => cacheAssetTree(cache, url, canonicalUrls)),
);
await Promise.all(
(await cache.keys())
.filter((request) => !canonicalUrls.has(request.url))
.map((request) => cache.delete(request)),
);
}
self.addEventListener("install", (event) => {
event.waitUntil(
caches
.open(CACHE_NAME)
.then(cacheDocumentAssets)
.then(() => self.skipWaiting()),
);
});
self.addEventListener("activate", (event) => {
event.waitUntil(
caches
.keys()
.then((keys) =>
Promise.all(
keys
.filter((key) => key.startsWith(CACHE_PREFIX) && key !== CACHE_NAME)
.map((key) => caches.delete(key)),
),
)
.then(() => self.clients.claim()),
);
});
self.addEventListener("fetch", (event) => {
const request = event.request;
if (request.method !== "GET") return;
const url = new URL(request.url);
if (url.origin !== self.location.origin) return;
if (request.mode === "navigate") {
event.respondWith(
fetch(request)
.then(async (response) => {
if (response.ok) {
const cache = await caches.open(CACHE_NAME);
await cache.put("./", response.clone());
}
return response;
})
.catch(async () => {
const cached = await caches.match("./");
return cached ?? Response.error();
}),
);
return;
}
event.respondWith(
caches.match(request).then(
(cached) =>
cached ??
fetch(request).then(async (response) => {
if (response.ok && response.type === "basic") {
const cache = await caches.open(CACHE_NAME);
await cache.put(request, response.clone());
}
return response;
}),
),
);
});
+1 -1
View File
@@ -3,7 +3,7 @@
"schemaVersion": 1,
"id": "de.add-ideas.sudoku-tools",
"name": "Sudoku Tools",
"version": "0.1.0",
"version": "0.2.0",
"description": "Set, play, solve and analyse Sudoku puzzles locally in the browser.",
"entry": "./",
"icon": "./favicon.svg",