Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

95 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

KnightClub

KnightClub is an original, local-first chess studio for playing, reviewing and improving without a subscription, account, telemetry or online multiplayer.

The current vertical slice provides a legal chess board with click or drag movement, local hot-seat play, real White/Black/Random side selection against named local opponents, a single safe premove while the bot thinks, accurate preset or custom chess clocks, resignation and draw agreements, original optional move sounds, three bounded Stockfish targets plus Elo/custom resource controls, session recovery, result-aware PGN/FEN import and explicit local transfer, a completed-game library, interactive MultiPV analysis and progressive full-game review with accuracy, move classes, turning points and evidence-backed coaching. Each original opponent owns a strictly legal local opening cue; a matching standard-start route applies that authored move without starting an engine. When chess rules leave the bot exactly one legal reply, that move is also applied locally without launching Stockfish. Later, a profile may choose only a legal, close second principal variation that comes from the same bounded Stockfish search; a limited-strength bestmove, mate, bound, stale or malformed candidate always keeps Stockfish’s original move. Train includes an original, no-spoiler offline Tactics Sprint, personal saved-line practice and board-vision drills. The website runs Stockfish 18 Lite locally through WebAssembly; the Tauri app uses a separately installed native Stockfish executable. Desktop state is kept in a private versioned SQLite database and the browser uses bounded localStorage. Engine work never runs on the React UI thread, and stale searches are rejected before they can alter a newer position.

The first board paint is deliberately quiet: Stockfish and the KnightBot fallback are created only for a real bot move or an explicit Verify engine action. A matching opening cue or rules-proven only legal reply needs neither engine. Large private history is also opt-in: Play reads neither the retry queue, saved-game library nor tactics progress; Library/Insights and Train hand their raw snapshots to short-lived local Workers only after the player opens that workspace, and each surface shows an honest preparation state instead of flashing an empty result. Named-profile midgames request two PVs through one existing single-threaded, node-bounded search—not a second go command. Every live bot move, including Elo and Custom profiles, is hard-capped at its level's low-compute Easy 50 ms/1k nodes, Balanced 50 ms/1.5k or Strong 60 ms/3k ceiling with one thread and 16 MB Hash; a short cancellable display floor keeps lightweight searches from looking unnaturally instant without consuming extra engine time. Fresh games start with a compact opponent/side/time summary and a board-adjacent Setup control, so a player can move immediately or reveal the full configuration on demand; after a new-game setup, the panel still closes after the first ply so draw, resign and notation stay within reach. Optional move sounds use layered tactile board synthesis and unlock from the player's first board interaction. Custom-time fields retain their draft locally until Use custom time, so normal typing does not re-render the whole game shell and restored custom values remain editable. Live clock repainting is isolated from the Play shell, and memoized square wrappers keep selection, drag, premove and keyboard-focus changes from rebuilding every board button. A started timed game that remains live outside Play gets an equally isolated dock with both clocks and a one-tap return control, rather than a root-shell refresh on every tick. Each immutable Play position shares one verbose chess.js history snapshot; current-position copies use a platform deep snapshot with a rules-safe replay fallback, while a new move extends the known notation incrementally. A read-only historical preview directly constructs the selected move’s stored exact after FEN instead of replaying its prefix; malformed external FEN safely falls back to the established replay path. The Play notation groups those moves into memoized rows and uses one delegated selection handler, so appending a move or opening a historical position updates only the affected rows rather than rebuilding every old SAN button. Review's read-only board, per-row notation and mobile move-picker options are likewise isolated from progress-only full-review updates. Its ambient candidate line starts as Quick/one-line work after a short idle pause and reuses a bounded, session-only exact-position cache keyed by runtime, configured engine path and effective settings, so revisiting a position does not launch another search; full-game review instead reuses an intermediate post-move analysis as the next move's baseline, with no cache shared across jobs. Review, Train and Insights load only when opened, with hover/focus prefetch and a clear local reload recovery if an async workspace asset is unavailable. On desktop, startup restores only the active session, preferences and a bounded game count; complete PGNs are decoded on demand when Library or Insights is actually opened, while any one-time browser-mirror import uses the same deferred worker boundary. A live bot move takes priority over optional review analysis, and desktop Verify reuses that same managed Stockfish supervisor rather than spawning a competing process. In the browser, once a game is finished or the player has safely left Play, its idle Stockfish/fallback Workers are released; live bot replies and manual verification retain priority. On desktop, contiguous not-yet-started active-session snapshots coalesce to the latest state without crossing ordinary persistence or clear-session boundaries. Persistent browser and desktop engines reuse only acknowledged unchanged UCI option blocks while retaining a readiness fence for every search; a normal cancellation keeps the warm runtime and drains late output through that fence, while failures and timeouts still recreate it. Native Play keeps a monotonic cancellation watermark while native analysis uses renderer-wide increasing IDs and consumes its exact cancellation marker as its request observes or completes it.

Fast live PGN: Play now serializes export and autosave PGN directly from its already-cached verbose move snapshot instead of calling chess.js's history-rewinding pgn() path after every move. Comment-free live games also recognize chess.js's known-empty comment store before asking getComments(), avoiding that API's otherwise unnecessary full-history prune on every move. Standard games, Black-to-move setup positions, promotions and result/termination headers remain compatible; an annotated or unfamiliar comment-store shape deliberately falls back to the conservative authoritative chess.js serializer.

Smooth active-session recovery: a browser session over 2,048 characters now paints Play immediately with a clear Restoring your saved game state, then a one-shot latest-wins Worker parses JSON, replays PGN and prepares its verbose move list away from the interaction thread. The Worker returns a verified structured-clone chess-state snapshot rather than only a final FEN, so undo, repetition, headers/comments and complete notation survive without replaying the game again on the UI thread. While recovery is pending, input, shortcuts, bot work and autosave are locked so the visible fallback cannot overwrite the real game; the browser raw mirror is rechecked after success or failure before adoption. A malformed, over-limit or continually changing deferred mirror stays protected behind an explicit Start fresh instead choice rather than being silently replaced. On desktop that choice also fences an in-flight SQLite bootstrap or browser-mirror migration, suppresses stale session hydration/import and queues a final native clear behind migration writes, so a game the player rejected cannot reappear. Desktop never first adopts a browser session: SQLite bootstrap remains authoritative, and the browser mirror is touched only during an empty-database migration. Active-session PGNs are capped at 512 KiB and raw browser payloads at 1 MiB.

Paint-first saved-game opening: choosing a large Library record still asks for confirmation immediately, but its accepted PGN now moves to a separate latest-wins local Worker only after Play has rendered Opening saved game. The existing board remains visibly paused and unavailable; Cancel opening, leaving Play, a newer request or an invalid/late result leaves that game intact. The verified snapshot keeps complete notation, undo, repetition and annotations, and creates the canonical review link off the interaction thread. Small saved games retain the instant path.

Large private libraries: Library searches and filters always consider the full local result set, then display the first 24 matching games with an explicit Show more control. This keeps opening and refining a 500-game history responsive without hiding older matching games; repeated text searches reuse each game's local search text.

Fast review completion: A newly finished game receives its deterministic review link directly from the verbose move history already in memory. Older library games receive the same link only while their PGN is already being opened. Saving a completed review therefore updates matching lightweight library metadata without replaying every stored PGN on the UI thread.

Responsive Review and personal training: Small current games still open with their exact timeline and selected position immediately. A longer initial PGN, every pasted PGN and every selected notation file instead paints a clear local loading state first, then performs the same bounded chess.js replay in a latest-wins dedicated Worker; a newer import, handoff or unmount terminates stale work, and live-game continuation checks follow the same background path. Review fixes that initial Worker-routing decision on mount, so full-review progress does not repeatedly UTF-8 scan the immutable PGN; its visual bar remains continuous while assistive technology hears only five-percent milestones. A completed report paints its scorecards before its detached persistence snapshot is prepared. If it contains eligible practice moments, a short-lived dedicated Worker performs the strict bounded replay and Review adopts only a response that still exactly matches the current timeline into a private immutable snapshot; the visible practice action stays safely disabled with a polite checking state until then. Cursor movement and selected/batch prompt controls then look up the chosen ply rather than replaying the entire game again. The standalone retry boundary still performs its full fail-closed replay. In the browser, first entry into Train hands the bounded raw retry mirror to its own one-shot local Worker rather than making Play validate every saved FEN and solution line; desktop uses that same worker for its background legacy-mirror migration. A Review-to-Train handoff shows a preparation status instead of falsely declaring an empty queue. The raw text is rechecked before adoption, and any just-saved in-memory practice item is merged so it cannot blink away. A personal queue reconstructs only the selected saved line, shows 24 practice positions at first and adds 24 on request; a linked or current position remains visible even when it is deeper in the queue. Browser retry persistence keeps a private, raw-storage-versioned canonical snapshot after its first fail-closed synchronous boundary validation, so completing, skipping or deleting a prompt validates only the changed item rather than replaying hundreds of retained positions; any external storage change is fully revalidated. Browser saved-review storage likewise uses a shallow, raw-text-versioned index and fully replays only a new record or the requested same-key candidate, so opening one report does not replay an entire 500-report history. This avoids a burst of hundreds of chess.js replays and keeps long private training queues easier to scan.

Long-game notation and Review resources: Normal Play games retain their complete notation. For unusually long games, both Play and Review initially mount the newest 40 move rows and offer Show earlier moves in 40-row steps; an older selected Review position stays pinned and accessible without remounting the omitted history. Full-game Review is deliberately limited to 1,024 plies—the persisted-report bound—so an oversized imported game stays browsable and available for single-position analysis without launching thousands of sequential searches. Its mobile picker changes to a bounded numeric ply jump instead of mounting thousands of native options. In the browser, an uncached Review analysis or full-game Review releases an idle Play Stockfish runtime immediately before it starts its own engine Worker; the Review runtime is released after completion, stop or a superseding task. This prevents two browser WASM engines and hash allocations competing on the same lower-power device.

Try ideas directly in Review: Explore this position opens a clearly labelled temporary variation anchored to the exact replay FEN. The board then accepts legal click or drag moves (including an explicit promotion choice), and the existing bounded candidate-line request/cancellation/cache path evaluates the displayed branch position. Undo, reset and return-to-game controls make the boundary obvious; any main-line navigation, import or full-review run exits the branch. Its FEN and, after at least one move, standalone SetUp/FEN PGN can be copied or downloaded locally. Temporary variations never alter, save over or claim to be annotations on the original game.

Play shortcuts: N starts a new game, U or ⌘/Ctrl+Z undoes a turn, F flips the board and Escape cancels a selection, promotion or queued premove. Away from the board grid and text fields, / step through Play history, with the newest position returning to live play. A promotion chooser focuses Queen when it is offered and accepts Q, R, B or N for an immediate legal choice. When a confirmation or promotion dialog is open, only its own controls reach the paused game, so an unrelated shortcut cannot alter the position behind it.

Local PGN/FEN transfer: Play offers Copy PGN and Download PGN for the current game, with immediate feedback beside its toolbar. Its Position tools offer Copy current FEN and Download FEN. Review accepts pasted notation or a deliberately selected local .pgn, .fen or .txt file through a keyboard-focusable picker, and a temporary Review variation exports its displayed FEN or self-contained branch PGN rather than the main line. Transfer remains on-device; an unavailable Clipboard API safely falls back or reports a clear status, and a rejected or stale file import cannot replace the active Review timeline.

Current low-power bot tuning (2026-07-23): Easy uses 50 ms / 1,000 nodes, Balanced 50 ms / 1,500 nodes, and Strong 60 ms / 3,000 nodes. The browser adapter and native UCI command both apply those ceilings to every live Play request, including Elo and Custom profiles, force one thread / 16 MB Hash / baseline one PV and convert an omitted node cap into the finite level limit. A named opponent can raise only that same search to two PVs. Lower valid time/node choices remain respected; no custom control can raise a Play reply above its level cap. Short 140/180/220 ms cancellable display floors keep bot replies legible without holding an already-finished engine search or making local opening cues feel stalled. After a full desktop Review settles, its shared native process is released only when no newer Play or Review task owns it, returning that Review Hash and Stockfish NNUE memory to macOS.

Default opening responsiveness (2026-07-23): Rowan Pike is the first selected opponent, so its conventional 1. e4 e5 2. Nf3 Nc6 route is now two real, chess.js-validated local moves. The common first interaction therefore paints and replies without booting the 7 MB browser Stockfish runtime; deviations still use the existing bounded local engine. Once a player leaves Play or a game ends, the same safe idle gate now releases either browser Workers or the desktop pool’s idle native child, returning its NNUE/Hash memory without interrupting a live move, premove, Verify or Review task.

Principles

  • Offline-first: all runtime assets are bundled and cached by the PWA build.
  • Private by default: games and preferences remain on the device.
  • Correctness before spectacle: chess.js is the legal-rules authority and edge cases are tested.
  • Original product: KnightClub implements general chess concepts without copying Chess.com code, content, branding or trade dress.
  • Engine isolation and restraint: browser Stockfish runs in a dedicated on-demand Web Worker and desktop Stockfish remains a separate UCI process; play defaults are single-threaded, node-bounded and cancellable. Both are GPLv3 components kept outside KnightClub's original source.

Quick start

Requires Node.js 22 or later. Browser/PWA development needs no chess engine.

npm install
npm run dev

Open the local address printed by Vite. The pinned Stockfish 18 Lite WebAssembly assets are prepared automatically and cached by production PWA builds for offline use.

Website publishing

The latest browser build is published directly from this repository at https://dingding-leo.github.io/KnightClub/. Pushes to main build the PWA with KNIGHTCLUB_BASE=/KnightClub/ and deploy dist/client through GitHub Pages.

macOS desktop with Stockfish

Install Rust and a local Stockfish executable, then run:

brew install stockfish
npm install
npm run tauri:dev

Open Engine settings in a bot game to verify automatic discovery or select an executable with the native picker. Presets need no UCI knowledge; Elo and Custom profiles preserve their strength identity, while every live bot move remains hard-capped to its selected level's low resource budget. Lower valid move-time/node choices are honoured; a Custom value cannot make a Play reply use extra threads, Hash, depth or unbounded search. Review retains its own explicit analysis strength controls and can use the configured desktop Threads/Hash. An invalid typed number stays out of saved state, receives an inline explanation, and never silently becomes the maximum CPU or memory setting; malformed stored values return to the corresponding safe default. On desktop, Verify reuses Play’s managed Stockfish supervisor and is held while a live bot move or another verification owns the single local engine task. KnightClub discovers an explicit configured path, KNIGHTCLUB_STOCKFISH, PATH, and common Homebrew locations in that order. The native Stockfish binary is not stored in this repository. If either engine runtime cannot start, the game safely falls back to KnightBot and reports that state in the UI.

Open Review to paste a PGN/FEN, select a local .pgn, .fen or .txt file, or load the current game. Per-position candidate lines update automatically in both the website and desktop app. Review full game runs local Stockfish sequentially, reusing every intermediate post-move MultiPV result as the next move's baseline instead of searching that position twice; only the final non-terminal position needs a one-line after check, and terminal positions stay in the rules layer. Each fully classified non-final move immediately yields a clearly labelled provisional checkpoint: players can inspect that feedback while analysis continues, then Stop & keep, Resume review with the same bounded settings, or Discard it. These checkpoints stay only in the current session and are never saved or offered to Training; a completed report alone is retained. When the same canonical game is reopened, KnightClub first checks its saved local report and keeps the costly review action disabled until that lookup settles; a restored report makes the deliberate rerun action read Review again. Selecting an inaccuracy, mistake, miss or blunder also shows a Coach's evidence card when the rules layer can prove a missed mate, check, unsupported moved piece, direct double attack or absolute king pin; its named squares light up on the board, and Left/Right (Home/End) navigate the replay. Otherwise it gives an honest comparison with the recorded line.

Eligible, normal-confidence review errors can be added to a private From your games practice queue from Review. A new prompt may snapshot the bounded solutionLineSan Stockfish principal variation saved by that completed review—not the moves that actually followed the error in the original PGN. Train reconstructs that exact pre-error position locally, fully validates the saved line with chess.js, and withholds future moves until they are reached or deliberately revealed. It asks for each recorded player move and auto-plays only the saved opponent PV reply; a legacy empty saved PV remains a first-move exercise, while a malformed non-empty PV is unavailable rather than silently shortened. Only an unassisted replay of the whole saved line advances the item once; an alternate, hint-assisted completion, reveal or skip keeps it due without a fresh engine comparison. The in-progress line is session-only, while the durable retry schedule remains in browser localStorage or desktop SQLite even if an older completed report is later pruned. Back to review returns to the exact reviewed ply whenever its saved source review remains available. Durable cross-restart analysis jobs and deeper tactical proof remain later Phase 2 work.

Verification

npm run lint
npm run typecheck
npm test
npm run build
npm run test:rust
npm run tauri:build

Current architecture

  • React 19 + strict TypeScript
  • Vite + offline PWA service worker
  • chess.js for rules and PGN/FEN handling
  • A pure timestamp-based TypeScript clock state machine with increment, delay, pause and timeout results
  • A pure player-side domain that resolves White/Black/Random bot games and keeps game ownership separate from board orientation
  • A unified completion domain for timeout, resignation and draw agreement plus standards-compatible PGN results
  • Lazy Web Audio synthesis for original move/capture/check/end cues; no third-party sound assets
  • Stockfish 18 Lite WebAssembly in a dedicated browser Worker, with pinned checksums and offline PWA caching
  • Latest-wins dedicated Workers for long Review PGN timeline preparation, transient retry-timeline verification and on-demand private Train retry hydration
  • Tauri 2 Rust command boundary and supervised native Stockfish UCI process
  • Persisted, independently validated Stockfish path, profile and resource/search settings
  • Lazily created Dedicated Web Worker for the built-in bounded failure fallback bot
  • Versioned bundled SQLite persistence on desktop with transactional legacy import and corrupt-file backup
  • Bounded browser localStorage compatibility fallback
  • Vitest + Oxlint + GitHub Actions CI

See docs/ARCHITECTURE.md, docs/STOCKFISH_INTEGRATION.md, docs/PRODUCT_SPEC.md and ROADMAP.md for the implementation and product plan.

Licensing

KnightClub's original source is source-available for non-commercial use under the PolyForm Noncommercial License 1.0.0. Commercial use requires a separate written licence. Stockfish.js and Stockfish remain GPLv3 and are distributed under their own terms, with the exact browser source revision, checksums and licence recorded in THIRD_PARTY_NOTICES.md.

About

A comprehensive, fully offline international chess studio implemented with a native Stockfish engine backend.

Resources

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages