Skip to content

Releases: YDW99/Regalia

Regalia v1.2.2

Choose a tag to compare

@YDW99 YDW99 released this 14 Jul 17:17
0b01956

What's New in v1.2.2

Full Changelog: https://github.com/YDW99/Regalia/releases/tag/v1.2.2/compare/v1.2.1...v1.2.2

Contributors

Bug Fixes

  • fix;docs;build;chore (#45) (@YDW99) 0b01956

Installation

Download the APK from the assets below and install it on your Android device.
Note: This app requires Stockfish chess engine binaries (libstockfish.so) to be built separately via NDK.

⚠️ Note: If you encounter any issues, please report them here.


Regalia v1.2.2 — Hardened by an 8-Skill Audit

A precision patch release: one real defect fixed, seven false positives cleanly dismissed, and the version bumped from 1.2.11.2.2. No new features, no new permissions, no new network access — just a sharper, better-defended build of the same chess experience you already trust.

SonarQube Cloud
Lines of Code
Security Rating
Maintainability Rating
Vulnerabilities
Signing
Engine: Stockfish 18
License: AGPL v3
License: GPL v3
Platform: Android
Min SDK: 23
Target SDK: 35
Xiaomi HyperOS 3


📦 Download

Asset Size Purpose
Regalia-v1.2.2-release.apk ~75 MB Standalone installer for arm64-v8a devices
Regalia-v1.2.2-manual-en.html ~1.1 MB Self-contained English user manual
Regalia-v1.2.2-manual-zh.html ~956 KB Self-contained Chinese user manual

💡 Bilingual parity — every user-facing string, every changelog entry, and every manual page is synchronized across Chinese (zh) and English (en). The app auto-detects system language; switch manually anytime via the ↔️中 / ↔️EN button in the header.


🔐 Integrity Checksums

APK file:           Regalia-v1.2.2-release.apk
APK versionCode:    122
APK versionName:    1.2.2
APK signature:      v1 ✅  v2 ✅  v3 ✅  (Xiaomi HyperOS 3 compatible)

Stockfish engine SHA-256 (must match all three locations):
  Source binary:    8f7116d3f1a7004a6581d4fb0c1ff891ce095bab6d45e52f1578897cf23b61b5
  jniLibs copy:     8f7116d3f1a7004a6581d4fb0c1ff891ce095bab6d45e52f1578897cf23b61b5
  APK-embedded:     8f7116d3f1a7004a6581d4fb0c1ff891ce095bab6d45e52f1578897cf23b61b5

Verify on your own machine:

# APK signature (v1 + v2 + v3 must all be true)
apksigner verify --verbose Regalia-v1.2.2-release.apk

# Version info
aapt dump badging Regalia-v1.2.2-release.apk | head -2

# Engine SHA-256 (must equal the value above)
unzip -p Regalia-v1.2.2-release.apk lib/arm64-v8a/libstockfish.so | sha256sum

🩺 What Happened in This Release

v1.2.2 is the direct result of subjecting v1.2.1 to an 8-skill comprehensive audit. Five sub-reports — totaling 4,199 lines of analysis — were produced using the following skill stack:

# Skill What it does
1 secure-code-review OWASP Top 10 (A01–A10) full-spectrum review
2 code-vuln-audit Secret-leak scan + OWASP pattern matching
3 deep-module-refactor God Module friction-point analysis
4 code-arch-optimizer Coupling matrix + refactoring roadmap
5 git-repo-audit Hot-file analysis + contributor metrics
6 code-to-chart Module dependency graph generation
7 code-safety-audit OWASP pattern sweep (45 sites)
8 web-security-audit WebView / XSS / network review

Every single finding from those reports was then verified line-by-line against the actual source tree — not against the stale snapshot the auditors saw. The result:

7 findings were false positives. 1 finding was real and is now fixed. The architecture recommendations are real but deferred.

This is what a disciplined patch release looks like.


✅ The One Real Fix

YELLOW-1 — FEN Parsing Lacks Length Limit

Attribute Value
OWASP category A03 — Injection
Severity 🟡 YELLOW (medium)
File src/main/assets/chess.src/tablebase.js
Function fenToState(fen)
Risk if unfixed Pathologically long FEN strings could cause unnecessary string processing before validation rejected them — a low-impact DoS vector
Fix Added a 200-character length limit at function entry

Why 200 characters?

A standard FEN is at most ~87 characters. The generous 200 ceiling comfortably accommodates:

  • ✅ Chess960 Shredder castling notation (e.g. HAah)
  • ✅ En passant target square (e.g. e3)
  • ✅ Extended fields (move counters, half-move clock)
  • ✅ Reasonable user input noise (trailing whitespace, etc.)
// tablebase.js — fenToState()
function fenToState(fen){
  if(!fen || typeof fen !== 'string') return null;
  if(fen.length > 200) return null;   // ← NEW: DoS guard
  const parts = fen.trim().split(/\s+/);
  // ...existing validation continues from here
}

Why the audit's regex was rejected

The audit suggested a strict character whitelist:

// ❌ Audit's suggestion — REJECTED
if (!/^[1-8PNBRQKpnbrqk\/\s-]+$/.test(fen)) return null;

This regex would break legitimate chess functionality:

Use case Legal characters Caught by regex?
Chess960 castling rights (KQkq or AHah) A-H, a-h ❌ Breaks
En passant target square (e3, g6) a-h, 1-8 ❌ Breaks
Field separator ( ) whitespace ✅ Passes

The existing per-character validation already rejects invalid piece characters by returning null — that is the correct whitelist approach, applied at the right granularity. Adding a coarse regex at the top would have created false negatives for valid Chess960 positions.


🚫 The Seven False Positives — and Why

Each of the following audit findings was carefully verified against the current codebase and confirmed to be either (a) based on stale code from previously deleted files, or (b) describing a defense that already exists.

RED-1getStatsPayload() returns unescaped PGN data (XSS)
Audit's claim StatsActivity.getStatsPayload() returns raw PGN strings that could contain <script> tags, causing XSS in stats.html.
Reality stats.html already escapes every PGN header value (including White / Black player names) via _escFEN() at the rendering layer — see line 1770.
Why the audit's fix was rejected The suggested Java-layer JSON escaping ("\") would corrupt JSON syntaxgetStatsPayload() returns a JSON string, and double-escaping quotes would break JSON.parse() on the JS side.
Status ✅ False positive — existing defense confirmed.
RED-2shouldOverrideUrlLoading doesn't block javascript: protocol
Audit's claim ChessWebViewClient.shouldOverrideUrlLoading() doesn't explicitly check for javascript: URLs.
Reality The current implementation uses a fall-through blocker: it allows only file:///android_asset/ and http(s)://, then return true (block) for everything else — including javascript:, data:, intent:, content:, and about:blank.
Why the audit's fix was rejected The audit's suggested early if (url.startsWith("javascript:")) return true; is functionally redundant — the final return true already catches it. Adding it would be harmless but misleading (implying the block list is opt-in rather than default-deny).
Status ✅ False positive — default-de...
Read more

Regalia v1.2.1

Choose a tag to compare

@YDW99 YDW99 released this 14 Jul 02:38
81cd0b6

What's New in v1.2.1

Full Changelog: https://github.com/YDW99/Regalia/releases/tag/v1.2.1/compare/v1.2.0...v1.2.1

Contributors

Bug Fixes

  • fix;docs;style;build (#43) (@YDW99) 83cc208

Build / CI

  • Update BUILDING.md with v1.2.1 notes and remove AI-GEN (@YDW99) 81cd0b6

Installation

Download the APK from the assets below and install it on your Android device.
Note: This app requires Stockfish chess engine binaries (libstockfish.so) to be built separately via NDK.

⚠️ Note: If you encounter any issues, please report them here.


Regalia v1.2.1 — Release Notes

A standalone, open-source chess app for Android — play offline against Stockfish 18, analyze your games, and explore openings. No account, no network, no tracking.

SonarQube Cloud
Lines of Code
Security Rating
Maintainability Rating
Vulnerabilities
Signing
Engine: Stockfish 18
License: AGPL v3
License: GPL v3
Platform: Android
Min SDK: 23
Target SDK: 35
Xiaomi HyperOS 3


📦 Download

File Size Description
Regalia-v1.2.1-release.apk ~75 MB Release APK — arm64-v8a only, Android 6.0+
Regalia-v1.2.1-manual-zh.html ~972 KB Chinese user manual (self-contained HTML)
Regalia-v1.2.1-manual-en.html ~1.1 MB English user manual (self-contained HTML)

⚠️ The engine binary is NOT included in the source tarball.
If you build from source, download the official Stockfish 18 arm64-v8a-dotprod binary separately. See BUILDING.md for instructions.


✨ What's New in v1.2.1

v1.2.1 is a same-version refinement release — no new features, no new permissions, no versionCode bump. It is the result of 15 rounds of iterative quality improvement on top of the v1.2.0 architecture refactor, driven by first-principles code audits, SonarCloud static analysis, and guidance from three reference PDFs (AI Code Generation Defect Prevention, Android WebView Development, SonarCloud Perfect Review).

🎯 Highlights

  • 3 SonarCloud Bugs fixed (PR #43): 2 real S3923 "if/else identical" issues + 1 S2757 false-positive refactor
  • 152 empty catch blocks eliminated (S108): 146 in chess.src/*.js + 6 in stats.html — all now log module-tagged console.warn
  • God Function refactors (S3776): renderInternal 347→23 lines, _renderDialogs 181→31 lines, _renderReviewMode 612→561 lines
  • 53 typeof modernizations (S2703): typeof x === 'undefined'x === undefined for declared variables; true globals (crypto, AndroidBridge) correctly preserved
  • 2 user-reported bugs fixed: review eval chart not refreshing + stats page data completeness
  • 25 style unifications (S3523/S1154): parseFloatNumber.parseFloat, String.fromCharCodeString.fromCodePoint
  • 4 nested ternary refactors (S3358) + 2 duplicate CSS selector merges (S3646)

♟️ Features

Core Gameplay

Feature Description
Stockfish 18 engine World-class chess engine, arm64-v8a-dotprod build (ARMv8.6-A DOTPROD instruction set for accelerated NNUE inference)
8 difficulty levels Level 1 (beginner) → Level 7 (Skill Level mode) → Level 8 (⚙️ custom engine config)
Chess960 (Fischer Random) Full Chess960 support with SP-ID selector, back-rank preview, Shredder-FEN castling rights
Time controls Untimed / Sudden Death / Fischer Increment / Bronstein Delay / US Delay
Ponder mode Engine thinks on opponent's time for faster response
Undo/Redo Full move history navigation with redo stack
Setup mode Custom board positions with manual castle-rights (🔁) and en-passant (⚡) markers

Analysis & Review

Feature Description
Review mode Step through your game move-by-move with eval bar, trend chart, and move slider
Analyze All Batch-evaluate every move in the background; long-press to prioritize a specific step
Evaluation trend chart SVG line chart showing eval progression; auto-centers on the active move
Visual annotations [%csl] square highlights (B/R/G/Y) + [%cal] arrows; supports multi-color per square
Control heatmap 🌗/🌈 Board coloring showing piece control influence per square
Critical moves Auto-detected moves where evaluation changed significantly
Statistics page 📊 Full-game statistics: move quality distribution, eval trend, opening classification, PGN text

PGN Support

Feature Description
PGN import/export Standard PGN (1994 spec) with NAG, [%csl]/[%cal], [%eval], [%clk], [%emt]
PGN cache manager 📚 Save/load games locally with tags; partial-eval coverage dialog
FEN import/export Copy/paste FEN positions; setup-mode FEN with Shredder castling for Chess960
ECO opening classifier 500+ openings with search, family filter, and AI book moves
Lichess Syzygy tablebase 7-piece endgame tablebase queries (WDL/DTZ) via TLS-pinned API

User Experience

Feature Description
Bilingual UI 🇨🇳 Chinese / 🇬🇧 English, toggle from header; persists across sessions
Dark/Light theme Auto-follows system setting; optimized palettes for both modes
Sensor anti-shake Board stabilization via sensor fusion (optical-image-stabilization principle)
Haptic feedback Distinct vibration patterns for piece move, capture, check, game-over
Sound system Per-piece-type sound effects with move/capture/check/game-over variations
Coordinate labels a-h file labels + 1-8 rank labels + per-square coordinates on all boards
Responsive layout Portrait + landscape; notch/cutout/R-corner safe-area insets
SAF file picker Storage Access Framework for PGN export/import (no storage permission needed on API 29+)

🔒 Privacy & Security

Regalia is designed to be private by design.

  • 🚫 No account — no registration, no login, no user identification
  • 🚫 No tracking — no analytics, no telemetry, no crash reporting to remote servers
  • 🚫 No ads — no advertising SDKs, no ad network connections
  • 🌐 Offline-first — all gameplay is local; only optional tablebase queries use network (TLS-pinned)
  • 🔐 WebView hardened:
    • setAllowFileAccess(false)
    • setAllowFileAccessFromFileURLs(false)
    • setAllowUniversalAccessFromFileURLs(false)
    • @JavascriptInterface annotation on all exposed methods
    • URL scheme whitelist + sandbox path validation (JsBridgeGateway)
    • TLS 1.2+ enforcement + SPKI SHA-256 certificate pinning (TlsSecurityHelper)
  • 📁 Scoped storage — all file I/O via SAF content URIs (no direct file path access on API 29+)

Permissions

Permission Why
INTERNET Optional Lichess Syzygy tablebase queries (7-piece endgame)
ACCESS_NETWORK_STATE Check network availability before tablebase queries
WAKE_LOCK Keep engine alive during long analysis (foreground service, 30-min timeout)
VIBRATE Haptic feedback on moves/captures/checks
`FOREGROUN...
Read more

Regalia v1.2.0

Choose a tag to compare

@YDW99 YDW99 released this 12 Jul 02:45
bb4b609

What's New in v1.2.0

Full Changelog: https://github.com/YDW99/Regalia/releases/tag/v1.2.0/compare/v1.1.2...v1.2.0

Contributors

Bug Fixes

  • refactor;style;fix;docs;build (#40) (@YDW99) bb4b609

Installation

Download the APK from the assets below and install it on your Android device.
Note: This app requires Stockfish chess engine binaries (libstockfish.so) to be built separately via NDK.

⚠️ Note: If you encounter any issues, please report them here.

Regalia v1.1.2

Choose a tag to compare

@YDW99 YDW99 released this 09 Jul 09:50
455ab18

What's New in v1.1.2

Full Changelog: https://github.com/YDW99/Regalia/releases/tag/v1.1.2/compare/v1.1.1...v1.1.2

Contributors

Features

  • Add files via upload (@YDW99) 6c9ebcb

Bug Fixes

  • fix(bug);docs(main);build(v1.1.2);chore(ui…) (#37) (@YDW99) 0e89c35

Documentation

  • docs(CONTRIBUTING):完善细节 (@YDW99) 03b16a8
  • docs(CONTRIBUTING);security(#34);build(#33) | (#35) (@YDW99) 50e7b5c

Other

  • Delete CONTRIBUTING-en.md (@YDW99) f9bf776
  • Delete CONTRIBUTING-zh.md (@YDW99) 6a2fe04
  • Delete Manual/Regalia-v1.1.1-manual-zh.html (@YDW99) cb9176f
  • Delete Manual/Regalia-v1.1.1-manual-en.html (@YDW99) 455ab18

Installation

Download the APK from the assets below and install it on your Android device.
Note: This app requires Stockfish chess engine binaries (libstockfish.so) to be built separately via NDK.

⚠️ Note: If you encounter any issues, please report them here.


♟️ Regalia v1.1.2 ♔

A professional-grade Android chess app built around Stockfish 18 — now more robust, more secure, and more thoroughly audited than ever.

Lines Audited
Bug Fixes
Signing
License
Engine: Stockfish 18
Xiaomi HyperOS 3
SonarQube Cloud

Version 1.1.2 (versionCode = 112)
Release Date 2026-07-12
Min Android 6.0 (API 23)
Target Android 15 (API 35)
Engine Stockfish 18 arm64-v8a-dotprod
Architecture arm64-v8a only
HyperOS 3 ✅ Compatible
Source Size ~34,000 lines across 8 JS modules + 9 Java files + 1 JNI + 1 stats page

📦 Download

Asset Description
Regalia-v1.1.2-release.apk Signed release APK (v1 + v2 + v3 signature schemes). 75 MB.
Regalia-v1.1.2-manual-zh.html Self-contained Chinese user manual.
Regalia-v1.1.2-manual-en.html Self-contained English user manual.

⚠️ Engine binary is NOT in the source tarball. The 114 MB Stockfish 18 arm64-v8a-dotprod binary is excluded to keep the tarball small and avoid redistributing the engine with the source. Build instructions are in BUILDING.md (inside the tarball) — you'll download the engine separately and place it at src/main/jniLibs/arm64-v8a/libstockfish.so.


🎯 What's New in v1.1.2

v1.1.2 is a maintenance release that keeps the version number (versionCode = 112) unchanged while delivering 6 same-version revision phases (Phase 67 → 72). Each phase was driven by user feedback, first-principles code review, or security hardening — no new features were added, but the app is now significantly more reliable.

Phase Overview

Phase Date Theme Key Deliverable
67 2026-07-07 Version bump + PGN cache [%eval] root-cause fix + code review report New _pgnCacheShowPartialEvalDialog (3 options)
68 2026-07-08 Analyze All optimization + long-press priority + UI polish Long-press to prioritize a step during batch analysis
69 2026-07-09 4 bug fixes + Web Worker robustness + UCI optimization Phase 69 UCI caps (MultiPV 8, Overhead 1000ms, Hash 50%, Threads 2×)
70 2026-07-10 First-principles code review cleanup Edge-case [%eval] loss fix + console.log cleanup
71 2026-07-11 Stats-page move-selection bug fix + full code review CSP 'unsafe-inline' + Chess960 0-distance castling + concurrency fixes
72 2026-07-12 Review analyze-all "false completion" bug fix + manual accuracy Full-range completion scan + manual sync

🐛 Bug Fixes (User-Reported)

1. Stats Page Move Selection Was Broken (Phase 71)

Symptom: Clicking a PGN move (e.g. 1. e4) in the 📊 Statistics page did nothing — no highlight, no board switch.

Root cause: The stats.html Content-Security-Policy used a SHA-256-hash-based script-src policy. CSP Level 2+ silently blocked all 23 inline onclick event handlers because the policy didn't include 'unsafe-inline' or 'unsafe-hashes'. The <script> block ran fine (hash matched), but every onclick="selectMove(0)" was silently dropped.

Fix: Switched script-src from 'sha256-<hash>' blob: to 'unsafe-inline' blob:. This is safe because stats.html is a local asset (file:///android_asset/) with no externally injected content and all JavaScript is inlined. Bonus: removing the hash eliminates the recurring "CSP hash mismatch" bug class (Phase 69 Bug 4 was the same root cause).

2. Review "Analyze All" Falsely Reported Completion After Long-Press Priority (Phase 72)

Symptom: During an "Analyze All" batch, long-pressing an uncached move to prioritize it would sometimes cause the batch to report "all analysis complete" — even though earlier steps remained un-analyzed.

Root cause: The _reviewAnalyzeAdvance() completion check only walked forward from _reviewAnalyzeStep+1. When a priority eval jumped ahead (e.g., user long-pressed step 50 during a batch at step 5) and subsequent steps were cached, the forward walk reached _lastStep and the completion branch fired — even though steps 5–49 were still uncached.

Fix: When the forward walk finds nothing, scan the entire range [0.._lastStep] for the lowest uncached step and resume the batch from there. The forward-only fast-path is preserved for the common (no-priority) case to avoid an O(n) full scan on every step.

// Phase 72 fix — full-range completion scan
if (nextStep > _lastStep) {
  let _lowestUncached = -1;
  for (let i = 0; i <= _lastStep; i++) {
    if (!_reviewEvalCache.has(i)) { _lowestUncached = i; break; }
  }
  if (_lowestUncached >= 0) nextStep = _lowestUncached;
  // else: truly all cached → completion branch
}

3. PGN Cache Save Lost [%eval] Annotations (Phase 67 + 69 + 70)

Symptom: After running "Analyze All" in review mode, saving the game to the PGN cache would sometimes produce a PGN missing [%eval] annotations.

Root cause (three layers, fixed across three phases):

  • Phase 67: The coverage check was gated on !_useOriginal, but _useOriginal is almost always true for imported PGNs (because importPGN sets time:null). The check never ran, so the partial-eval dialog never appeared.
  • Phase 69: Race conditions in the PGN cache manager (save/import/delete/rename/tags) could corrupt state. Added _pgnCacheOpInProgress guard.
  • Phase 70: Even after the above, exiting review mode before saving would still lose [%eval] because the force-rebuild was gated on _inReview (now false). Fix: check _reviewEvalCache.size > 0 directly.

Fix: A new 3-option dialog (💾 Some steps not yet analyzed) appears when coverage is incomplete:

  • 🔬 Analyze All first (recommended) — runs the batch, auto-saves on completion
  • 💾 Save anyway — legacy behavior (evals will be missing)
  • ✖️ Cancel — returns to cache manager

4. Chess960 0-Distance Castling Nulled the King (Phase 71)

Symptom: For Chess960 SP-IDs where the king starts on its castling target square (e.g. king on g1, kingside rook on h1 → UCI g1h1), castling would silently remove the king from the board.

Root cause: uciToCoords rewrote the destination to col 6, producing a 0-distance "move" g1g1. The _castleSide distance heuristic rejected it (0 < minDist), castling wasn't detected, and makeMv ran board[to] = board[from]; board[from] = nullself-copy then clear = king nulled.

Fix (defense-in-depth, 4 files):

  1. ai-bridge.js uciToCoords — attach castle='kingside'/'queenside' to result.to
  2. ui.js executeMove — check to.castle as primary source (covers AI moves where legalMvs is empty)
  3. game-logic.js _castleSide — explicit 0-distance branch
  4. stats.html — mirror all three fixes in its independent code

🔒 Security & Robustness Hardening

XSS Hardening on Stats Page (Phase 71)

The CSP relaxation to 'unsafe-inline' (required for the onclick fix above) introduced a theoretical XSS vector: the renderPGNText movetext-walk fallback appended unrecognized characters raw to the HTML string. A malicious PGN like 1. e4 <img src=x onerror="..."> e5 * could execute.

Fix: All unrecognized movetext/variation/notation characters now route through _escFEN() (escapes `& < > " ' ``) before HTML insertion. Applied to:

  • renderPGNText movetext-walk fallback
  • Variation-text walk
  • firstMoves opening-plies list (defense-in-depth — notation is engine-generated)

Concurrency Fixes in StockfishNative.java (Phase 71)

Race Root Cause Fix
readyOkLatchHolder JS binder thread + executor thread both wrote the si...
Read more

Regalia v1.1.1

Choose a tag to compare

@YDW99 YDW99 released this 05 Jul 20:12
4aed18e

What's Changed

Full Changelog: v1.1.0...v1.1.1


🚀 Regalia v1.1.1 — Release Notes

Version: 1.1.1 · versionCode: 111 · Date: 2026-07-05
Engine: Stockfish 18 (arm64-v8a-dotprod, native build)
Min Android: 5.0 (API 21) · Target: Android 15 (API 35)


Overview

Regalia is a standalone, open-source chess app for Android — play offline against Stockfish 18, analyze your games, and explore openings. No account, no network, no tracking. Features full Chess960 (Fischer Random Chess) support, bilingual Chinese/English UI, personified move animations, and a comprehensive review/statistics system.

v1.1.1 is a quality-hardening release built on top of v1.1.0 (versionCode 110). While the version number increments, the focus is entirely on bug fixes, robustness, and architectural cleanup — no new user-facing features were added. Instead, 8 revision phases (Phase 59–66) were executed, each driven by user feedback and first-principles code audits.


What's New in v1.1.1

🔧 Bug Fixes

# Fix Impact
1 Duplicate every-5-moves PGN annotation — re-exporting an imported PGN no longer duplicates [%eval] / eval-description annotations. Whitespace-tolerant regex dedup. PGN round-trip fidelity
2 Step-0 eval not cachedonEngineEval now caches stale callbacks for the original step even when the user navigates before the callback arrives. The eval trend chart's data point at step 0 is no longer missing. Review mode correctness
3 Resignation/timeout comments hardcoded English — PGN comments now follow the app's global language (T() with new i18n keys). Bilingual PGN export
4 "Analyze All" sometimes incomplete — batch session decoupled from reviewStep via _reviewAnalyzeStep / _reviewAnalyzeGen / _evalRequestBatchGen. User navigation during batch no longer invalidates in-flight callbacks. Review analysis reliability
5 Visual annotation [%csl]/[%cal] PGN pollutionTRUE root cause found and fixed: reviewStates from a previous game's review session was not cleared in _resetGameUIState(), causing _computeAndCacheVisualAnnotations to use the old game's board state at the same move index. Fix: clear reviewStates on every new-game entry + gate the shortcut on reviewMode === true. PGN export correctness
6 Imported PGN not saving to PGN cache — pure-import games (no live moves) now save the original PGN text, preserving all comments/tags/NAGs. PGN cache manager
7 _savePGNYes timing bug — async export dialog could be open when the new game started. Fix: poll _pgnExportDialogActive before executing the pending action. Export flow reliability
8 Writer lock inconsistencycleanupEngineResources() / shutdown() / cleanupFailedEngine() now use _writerLock (consistent with Phase 58 heartbeat path). Engine concurrency safety
9 onRenderProcessGone infinite loop — crash-count backoff (max 3 recreates per 60s window). OEM ROM resilience
10 Integer_bitcount negative-input infinite loopn >>> 0 unsigned coercion. Defensive guard

🆕 New Features (minor)

  • Export annotation dialog — when exporting PGN (copy / file / cache save), a dialog asks whether to include special annotations ([%csl]/[%cal]/[%eval]/eval descriptions). Three options: "Yes, include special annotations" / "No, exclude special annotations" / Cancel. Supports Android back-button and haptic feedback.
  • Statistics board navigation buttons⏮ ◀ ▶ ⏭ below the stats board, context-aware (mainline vs variation).
  • Initial-position eval annotation — now a separate {} comment before the first move (not attached to white's first move).
  • Step-0 eval annotation[Initial position] prefix, deduped on re-export.

🏗️ Architectural Improvements

  • Centralized cache clearing_resetGameUIState() now clears 25+ state variables including _reviewEvalCache, _ecoRecCache, _pvCache, _visualAnnotationsCache, reviewStates, _preReviewSnapshot, setupHistory, and 7 dialog visibility flags. All 5 new-game entry points call it uniformly.
  • imported flag on visual annotations — distinguishes human-authored PGN annotations (imported=true) from auto-generated UI display aids (imported=false). Only imported=true entries are exported to PGN.
  • includeAnnotations parameter on _buildPGNString() — user-controlled annotation inclusion.
  • _requestBatchEval() — decoupled batch eval requests with generation counter, surviving user navigation and engine restarts.
  • _writerLock unification — all engineWriter access now goes through _writerLock, eliminating lock-inconsistency races.
  • Stale-state audit — following the reviewStates bug, audited all global state for similar patterns. Found and fixed _preReviewSnapshot, setupHistory/setupRedoStack, and 7 dialog flags not being cleared.

Phase-by-Phase Changelog

Phase 59 — Version bump + 4 bug fixes + analyze-all rewrite

  • Version bumped to versionCode=111, versionName="1.1.1"
  • Fix duplicate every-5-moves PGN [%eval] annotation (dedup via whitespace-tolerant regex)
  • Fix step-0 eval not cached (stale callbacks now cached for original step)
  • Add initial-position annotation to first move's {} comment with [Initial position] prefix
  • Fix resignation/timeout comments hardcoded English → T() i18n
  • First-principles rewrite of "Analyze All" batch logic: _reviewAnalyzeStep / _reviewAnalyzeGen / _evalRequestBatchGen decoupled from reviewStep

Phase 60 — Audit-driven fixes + stats board navigation

  • StockfishNative.java writer-close paths unified to _writerLock
  • Integer_bitcount() unsigned coercion
  • onRenderProcessGone() crash-count backoff (3/60s)
  • normalizeTagValue() tab filtering
  • Render-error page i18n (render_error_title key)
  • Navigation buttons (⏮ ◀ ▶ ⏭) below stats board (mainline/variation context-aware)
  • Static HTML export strips nav buttons (FEN-only); full-PGN export keeps them

Phase 61 — Cache pollution fix + PGN cache save + annotation move

  • _resetGameUIState() now centrally clears ALL game-related caches (15+ variables)
  • All 5 new-game entry points call _resetGameUIState() uniformly
  • _pgnCacheSaveCurrent() distinguishes pure-import vs live-play games
  • Initial-position eval annotation moved to separate {} comment before first move
  • Stats-page PGN sync verified

Phase 62 — Visual annotation imported flag

  • _visualAnnotationsCache entries now include imported boolean
  • Auto-generated annotations: imported=false (UI display aids, not exported)
  • Imported PGN annotations: imported=true (human-authored, exported)
  • _buildPGNString() only exports imported=true entries

Phase 63 — Export annotation dialog

  • _buildPGNString() accepts includeAnnotations parameter
  • _showPGNExportAnnotationDialog() shown before copy/export/cache-save
  • Three options: Yes / No / Cancel
  • 4 new i18n keys (bilingual zh/en)

Phase 64 — TRUE root cause fix: stale reviewStates

  • Root cause: _computeAndCacheVisualAnnotations used reviewStates[moveIdx+1] as a shortcut, but reviewStates from a previous game's review session was not cleared in _resetGameUIState()
  • Fix: clear reviewStates/reviewMode/reviewStep/reviewBaseState in _resetGameUIState()
  • Defense-in-depth: gate reviewStates shortcut on reviewMode === true
  • Export dialog text update: 💾 emoji, "特殊注释" / "special annotations" terminology

Phase 65 — Dialog back-button + haptics + stale-state audit

  • Export dialog: Android back-button support (equivalent to Cancel)
  • All dialog buttons: explicit HapticManager.fire('BUTTON_PRESS')
  • Stale-state audit: _preReviewSnapshot, setupHistory/setupRedoStack, 7 dialog flags — all now cleared
  • Overlay click-outside fixed: callback(null) (cancel) instead of callback(false) (No)

Phase 66 — Strict full-codebase audit

  • Bug fix: _savePGNYes timing (async dialog polling)
  • Redundancy: 2 stale console.log removals
  • Clarity: openStatsPage explicit includeAnnotations=true
  • Audit confirmation: all caches, flags, and declarations verified correct

Key Features

♟️ Chess Engine

  • Stockfish 18 (arm64-v8a-dotprod native build) — world-class engine
  • 8 difficulty levels (Lv.1–6 by ELO, Lv.7 Skill Level, Lv.8 Custom)
  • Real-time eval bar with depth, seldepth, nodes, NPS, and WDL
  • MultiPV analysis with 🌿Line variation display
  • Pondering support

🎨 User Interface

  • Bilingual Chinese / English (toggle with one tap, persisted)
  • Dark / Light mode (auto-follows system setting)
  • Personified move animations — each piece type has unique motion characteristics
  • Personified sound effects — each piece type has matching timbre
  • Control heatmap — per-square control visualization with SVG arrows
  • Eval trend chart — SVG line chart with pixel-perfect progress-bar alignment
  • Review mode — full game replay with per-step eval, visual annotations, and critical-move markers

📝 PGN Support

  • Full PGN import/export (1994 spec compliant)
  • [%eval] / [%csl] / [%cal] / [%emt] / [%clk] annotation support
  • Every-5-moves eval descriptions (bilingual, White-perspective)
  • Initial-position eval annotation (separate {} before first move)
  • Export dialog: user chooses whether to include special annotations
  • PGN cache manager (📚) — save/load games to app-private storage
  • Variations (RAV) import/export with 🌿Line display

🎯 Chess960

  • Full Fischer Random Chess support
  • ...
Read more

Regalia v1.1.0

Choose a tag to compare

@YDW99 YDW99 released this 05 Jul 06:47
f2727bb

What's Changed

Full Changelog: v1.0.9...v1.1.0


Regalia v1.1.0

A production-grade Android chess app built around Stockfish 18, with Chess960, time control, personified animations, bilingual UI, and a full review/statistics pipeline.

versionCode 110 · versionName 1.1.0 · Min SDK 21 · Target SDK 35 · arm64-v8a (dotprod)

License: AGPL v3
License: GPL v3
Engine: Stockfish 18
Platform: Android 5.0+
Zero dependencies


🎯 Release Highlights

This release ships 8 development phases (Phase 53 → 58) layered on top of the v1.0.9 baseline, delivering:

Category Count Notable Items
🐛 Bug fixes 20+ Chess960 castling rook-loss, PGN single-line parse, portrait review scroll, visual-annotation cache residue
🔒 Concurrency hardening 3 P0 stopLatch TOCTOU race, heartbeat deadlock, postJsCallback lifecycle guard
✨ New features 1 Every-5-moves PGN {} eval annotation (bilingual, White-perspective)
🎨 Visual refinements 6+ Check-response arrows, pixel-perfect chart alignment, nav-button centering
📝 Documentation 7 README.license + 2 HTML manuals + UBIQUITOUS_LANGUAGE.md All synced to v1.1.0

No version bump within the phase series — all changes ship under versionCode=110, versionName="1.1.0".


✨ What's New

Phase 58 — PGN Eval Annotation + P0 Concurrency Hardening

🆕 Every-5-moves PGN {} eval annotation

At moves 5, 10, 15, 20, …, the PGN {} comment now carries a human-readable fragment mirroring the in-app evaluation bar. The fragment is auto-localized via T() reading the global _lang variable, so it switches between Chinese and English based on the app's current language mode.

Format:

<White-perspective label> (<score>) D<depth> SD<seldepth> (<W%>W/<D%>D/<L%>L)

Examples:

Language Example
🇨🇳 Chinese 均势 (-0.10) D22 SD34 (1%W/96%D/3%L)
🇬🇧 English Equal (-0.10) D22 SD34 (1%W/96%D/3%L)
🇨🇳 Chinese (mate) 白方将杀 (#+3) D15 SD20 (100%W/0%D/0%L)
🇬🇧 English (mate) White mates (#+3) D15 SD20 (100%W/0%D/0%L)

Design choices:

  • White-perspective, not player-perspective — the PGN comment is unambiguous regardless of which side the human played. This matches standard PGN convention where [%eval] values are always from White's POV.
  • Missing components gracefully omitted — no depth → omit D## SD##; no WDL (all -1 or sum ≤ 0) → omit (%W/%D/%L).
  • Zero-sum WDL divide-by-zero guard — pathological wdl 0 0 0 engine output no longer produces NaN/Infinity.
  • Mate handling — uses #+N / #-N score + a localized "White mates" / "Black mates" label.
  • Position in {} comment — placed after the structured [%eval] tag (so [%xxx] tags remain first per PGN spec) and before free-text / resign / timeout annotations.

New API surface:

// pgn-standard.js
function formatEvalAnnotation(cached)  string
// cached: {eval, mate, depth, seldepth, wdlW, wdlD, wdlL}

// game-logic.js — new i18n keys (11)
pgn_white_winning, pgn_white_huge_adv, pgn_white_advantage,
pgn_white_slight_adv,, pgn_equal,
pgn_black_slight_adv, pgn_black_advantage, pgn_black_huge_adv,
pgn_black_winning, pgn_mate_white, pgn_mate_black

🔒 P0 Concurrency Fix 1 — stopLatch TOCTOU race

Symptom: In rare timing windows, the bestmove from a stopped ponder search could incorrectly arm the _discardingPonderBestmove flag, causing the next legitimate bestmove to be silently dropped — corrupting the engine state machine.

Root cause: The bestmove handler in readEngineOutput() read _stopLatch (a volatile field) without holding _stopLatchLock. This created a classic time-of-check-to-time-of-use race with stopAndWaitForBestmove's timeout path:

T1: bestmove handler reads _stopLatch = X (non-null)
T2: stopAndWaitForBestmove.await() times out
T2: stopAndWaitForBestmove sets _discardingPonderBestmove = true
T2: stopAndWaitForBestmove.finally{} clears _stopLatch = null
T1: bestmove handler calls X.countDown() and returns
    → discard flag is now stuck TRUE
    → NEXT legitimate bestmove is incorrectly discarded

Fix: The bestmove handler now atomically captures-and-clears _stopLatch under _stopLatchLock:

synchronized (_stopLatchLock) {
    stopLatch = _stopLatch;
    if (stopLatch != null) {
        _stopLatch = null;  // claim ownership
    }
}

The timeout path only arms the discard flag if it still owns the latch (_stopLatch == stopLatch under the lock). Exactly one consumer "owns" the latch — no race.

🔒 P0 Concurrency Fix 2 — Heartbeat deadlock

Symptom: shutdown() could block for the full 1-second join timeout when the heartbeat thread was mid-engineWriter.write(), leaving the heartbeat in a zombie state on some OEM ROMs.

Root cause: The heartbeat thread's engineWriter.write("quit\n") call (inside the zombie-detection branch) was synchronized on StockfishNative.this — the same monitor used by startHeartbeat() (which is synchronized). If shutdown() ran while the heartbeat held the this monitor inside the writer I/O call, shutdown's _heartbeatThread.join(1000) would wait for the heartbeat to release this — but the heartbeat was blocked on I/O.

Fix: Introduced a dedicated _writerLock (private final Object) for engineWriter access in the heartbeat path. _writerLock is decoupled from the this monitor, so shutdown's interrupt/join is not blocked by heartbeat's writer access.

// Before (deadlock risk):
synchronized (StockfishNative.this) {
    engineWriter.write("quit\n");
    engineWriter.flush();
}

// After (decoupled):
synchronized (_writerLock) {
    engineWriter.write("quit\n");
    engineWriter.flush();
}

cleanupEngineResources() and recoverEngine() use their own locks (_restartLock, _stopLatchLock) and do not hold _writerLock — no lock-ordering inversion.


Phase 57+ — Code-Review-Driven Preventive Hardening

Six preventive fixes surfaced by a comprehensive 12-skill parallel code review of the v1.1.0 source:

# File Fix
1 pgn-standard.js parseStandardPGN single-line PGN tag-stripping regex — /^\[[\s\S]*?\]/gm only matched the first tag when all tags + movetext were on ONE line (the ^ anchor with gm flags only matches the very start of the string). Replaced with format-strict, unanchored /\[[A-Za-z]\w*\s+"[^"]*"\]/g. Preventive — this parser is not on the main code path.
2 chess960.js isChess960CastlingLegal king-position lookup — was scanning the entire back rank (up to 8 board reads); now reads the cached s.wk/s.bk fields directly (maintained by syncHash() and cloneS()), with a defensive board-scan fallback.
3 ai-bridge.js WDL percentage display divide-by-zero guard — if(total > 0) check before dividing _sfWdlW/_sfWdlD/_sfWdlL by their sum.
4 StockfishNative.java postJsCallback activity-lifecycle guard — skips evaluateJavascript when host Activity isFinishing()/isDestroyed(). Prevents IllegalStateException crash on HyperOS 3 during engine-init retries after user exit.
5 EngineService.java Wake-lock bounded 30-minute timeout — wakeLock.acquire(30L * 60L * 1000L). Prevents indefinite CPU wake if OEM silently kills the service and onDestroy never runs.
6 res/README.license LIC-2 stale version reference — line 14 strings.xml description updated from v1.0.8 to v1.1.0.

Also added: UBIQUITOUS_LANGUAGE.md (pure-English, 80+ terms) at the project root — a domain-terminology glossary for developer/domain-expert conversations.


Phase 57 — Portrait Review Scroll + Visual-Annotation Cache

🐛 Portrait review move-list scroll positioning

Symptom: In portrait review mode, clicking a move in the move list scrolled to the wrong position (the active move was nowhere near centered, often clamped to the bottom).

Root cause: The Phase 56 fix (which replaced scrollIntoView with manual scrollTop computation) used _rAct.offsetTop to compute the active move's position. However, offsetTop returns the distance from the element's outer border to the top of its offsetParent's inner border — and .rmv-block's offsetParent is .review-overlay (position:fixed), not _rList (.review-moves has no position set).

  • Landscape: the error was small (~24px header).
  • Portrait: .review-moves is stacked below .review-left (the board column, 256–320px tall), so offsetTop included the board's full height — _target was way too large, clamped to scrollHeight - clientHeight (scrolled to bottom).

Fix: Replaced offsetTop with getBoundingClientRect()-based calculation relative to _rList:

_actTop = (_actRect.top - _listRect.top) + _rList.scrollTop;
_target = _actTop + (_actH/2) - (_listH/2);

Works identically for standard chess and Chess960 (pure DOM/layout fix, no game-logic changes).

🐛 Visual-annotation cache residue at review entry

Symptom: Stale [%csl]/[%cal] annotations from a previou...

Read more

Regalia v1.0.9

Choose a tag to compare

@YDW99 YDW99 released this 02 Jul 20:23
b028bca

What's Changed

Full Changelog: v1.0.8...v1.0.9


Regalia v1.0.9

A precision bug-fix release. Two critical correctness issues resolved, two silent visual-annotation defects closed, and the review eval chart now speaks one consistent blue-vs-red language across both themes.

License: AGPL v3
Engine: Stockfish 18
Platform: Android arm64-v8a
Min SDK: 21
Target SDK: 35
Xiaomi HyperOS 3


📦 Release Artifacts

Asset Size Description
Regalia-v1.0.9-release.apk ~74 MB Signed release APK (v1 + v2 + v3 signature schemes). Drop-in install on any Android 5.0+ arm64-v8a device.
Regalia-v1.0.9-manual-en.html ~813 KB Self-contained English user manual (embedded CSS, no external resources).
Regalia-v1.0.9-manual-zh.html ~714 KB Self-contained Chinese user manual (同步更新).

Verifier's note: The APK is signed with all three legacy schemes so it installs cleanly on Xiaomi HyperOS 3, which enforces stricter signature validation than stock Android. Verify with:

apksigner verify --verbose Regalia-v1.0.9-release.apk
# Expected:
#   Verified using v1 scheme (JAR signing): true
#   Verified using v2 scheme (APK Signature Scheme v2): true
#   Verified using v3 scheme (APK Signature Scheme v3): true

🎯 At a Glance

Version 1.0.9 (versionCode = 109)
Codename Phase 52 — Correctness & Clarity
Engine Stockfish 18 — arm64-v8a-dotprod (ARMv8.6-A DOTPROD instructions for NN inference acceleration)
Predecessor v1.0.8 (Phase 51, 2026.7.2)
ABI arm64-v8a only (modern 64-bit devices)
License Dual: AGPL v3 (original work) + GPL v3 (DroidFish-derived & Stockfish)

🔥 Headline Changes

This release concentrates on four root-cause fixes and one visual-design unification. No new features — every change is about making what already exists behave correctly.

🐛 Two CRITICAL Bug Fixes

1. PGN single-line import silently failed0 moves parsed, no error

Symptom

A PGN file where all headers and the movetext sit on one line (no \n between [Event "..."] and 1. e4) would import as zero moves. No toast, no warning — just an empty move list. The repro file PGN 2Kbug.pgn (71 half-moves, 11 variations, single line) triggered this 100% of the time.

Root cause

The tag-stripping regex used the multiline ^ anchor:

// BEFORE (broken)
let moveText = pgnText.replace(/^\[[^\]]*\]/gm, '').trim();

With the m flag, ^ matches at the start of each line. But in a single-line PGN, the entire string is one line — so ^ matches only once (at the very start). Only the first tag ([Event "Regalia"]) was stripped; the remaining tags ([Site "?"], [Date "..."], …) leaked into the movetext as invalid SAN tokens. The tokenizer skipped them all, hit the 5-consecutive-skip safety limit, and aborted.

Fix

Replace the line-anchored pattern with a tag-format-specific pattern that matches [TagName value] where TagName starts with a letter:

// AFTER (fixed)
let moveText = pgnText.replace(/\[[A-Za-z]\w*\s+[^\]]+\]/g, '').trim();

This is not anchored to line start, so it strips every tag regardless of PGN layout. It also cannot strip [%csl ...] / [%cal ...] / [%eval ...] inside brace comments — because % is not in [A-Za-z].

Companion fix — brace-comment stripping

The same single-line PGN exposed a second latent bug. The brace-comment stripper replaced {...} with empty string:

// BEFORE (broken) — e4{...}e5 became "e4e5" (one bogus token)
moveText = moveText.replace(/\{[^{}]*\}/g, '');

In 1. e4{[%emt 6:25:53]}e5, there's no space between } and e5. Stripping to empty produced e4e5 — a single invalid token that failed SAN parsing and triggered the same cascade-failure path.

// AFTER (fixed) — replace with a SPACE; whitespace normalization collapses the doubles
moveText = moveText.replace(/\{[^{}]*\}/g, ' ');

Verified: PGN 2Kbug.pgn now imports all 71 half-moves with all 11 variations intact.

2. Review & stats board showed "extra kings"phantom pieces appearing during PGN replay

Symptom

After importing a PGN and entering review mode (or opening the stats page), the board would sometimes display more kings than legally exist — a white king on g1 and a white king on e1, for example. The corruption worsened as you stepped through the moves.

Root cause — two independent defects

Defect A — _castleSide read the wrong board (game-logic.js)

The castling-detection fallback (used when the explicit mv.castle flag is absent — the common case for moves reconstructed from moveRecords during review replay) checked the global gameState.board and gameState.castlingRights:

// BEFORE (broken) — reads the FINAL state, not the state being moved
const _cr = gameState && gameState.castlingRights ? gameState.castlingRights : null;
const _destEmpty = !gameState || !gameState.board[mv.to.row][mv.to.col];

During PGN replay, the local state s being moved differs from gameState (which is the final state after all moves). Consider: if the white king ended the game on g1, then gameState.board[7][6] is non-empty (the king is there). When the replay reaches an earlier O-O move (king → g1), the _destEmpty check reads gameState (which has the king on g1) → returns false → castling detection is suppressed → only the king moves, the rook stays on h1.

Every subsequent move involving that misplaced rook then fails to replay, the board state diverges, and the user sees ghost pieces.

// AFTER (fixed) — accept an optional `s` parameter; use ITS board and rights
function _castleSide(mv, s) {
  // …
  const _st = s || (typeof gameState !== 'undefined' ? gameState : null);
  const _cr = _st && _st.castlingRights ? _st.castlingRights : null;
  const _destEmpty = !_st || !_st.board[mv.to.row][mv.to.row] || ;
  // …
}
// Callers updated:
//   makeMv(s, mv)        → _castleSide(mv, s)
//   makeMvInPlace(s, mv) → _castleSide(mv, s)
//   moveAlg(s, mv, …)    → _castleSide(mv, s)
// Animation-only callers in ui.js omit `s` and fall back to `gameState` (correct for live play).

Defect B — stats.html executeMove false-positive castling (stats.html)

The stats page's independent move executor used an overly broad castling detector:

// BEFORE (broken) — ANY king move to col 6 or 2 was "castling"
if (piece.type === 'king' && (move.to.col === 6 || move.to.col === 2)) {
  _isCastling = true;
  // …treat the piece on the destination as the "castling rook" and reposition it…
}

This caught normal king moves (Kf1-g1, Kg7-g6) and king captures (Kxg1). A king capture to col 6/2 would: (1) treat the captured piece as the "castling rook" and reposition it (silently losing the capture); (2) illegally displace the actual rook on h1/a1; (3) corrupt the board state — manifesting as extra pieces / extra kings.

// AFTER (fixed) — require ALL of: home row + correct distance + empty dest + right present
if (piece.type === 'king' && move.from.row === move.to.row) {
  const _homeRow = piece.color === 'white' ? 7 : 0;
  if (move.from.row === _homeRow && (move.to.col === 6 || move.to.col === 2)) {
    const _destEmpty = !state.board[move.to.row][move.to.col];
    const _cr = state.castlingRights || {};
    const _dist = Math.abs(move.to.col - move.from.col);
    const _is960 = (typeof gameVariant !== 'undefined' && gameVariant === 'chess960');
    const _minDist = _is960 ? 1 : 2;
    const _rightKey = ; // 'whiteKingside' | 'whiteQueenside' | 'blackKingside' | 'blackQueenside'
    if (_dist >= _minDist && _destEmpty && _cr[_rightKey]) {
      _isCastling = true;
      // …find and move the actual participating rook…
    }
  }
}

Verified: No extra kings appear at any review step for PGN 2Kbug.pgn (which contains 4 castling moves and many king moves to col 6/2).


🐛 Two HIGH-Severity Visual-Annotation Fixes

3. Variation comments contaminated main-line annotations

Symptom

After a variation containing [%csl] / [%cal] / [%eval] tags, the next main-line move would display the variation's squares/arrows/eval — as if those annotations described the main-line position. The review board was lying about the actual position state.

Root cause

The comment-extraction loop in _parsePGN read position-specific tags from {...} blocks but never checked the parenthesis depth _depth:

1. e4 (1. d4 {[%csl Bd4] [%eval 0.5]} d5 2. c4) e5 2. Nf3
            ↑ variation comment
                                              ↑ next main-line move — INHERITS the variation's an...
Read more

Regalia v1.0.8

Choose a tag to compare

@YDW99 YDW99 released this 02 Jul 06:27
8e530f4

What's Changed

Full Changelog: v1.0.7...v1.0.8


Regalia v1.0.8 — Personified Chess, Reimagined ♔

A standalone, open-source chess app for Android — play offline against Stockfish 18, analyze your games, and explore openings. No account, no network, no tracking.

versionCode 108 · versionName 1.0.8 · 30 development phases (Phase 22 → Phase 51)


🎯 What's New in v1.0.8

This is the biggest release in Regalia's history. v1.0.8 completely redesigns the move-animation and sound system around a single idea: every piece has a personality. A pawn shouldn't sound or move like a queen. Combined with full light/dark theme support and the Stockfish 18 arm64-v8a-dotprod engine, v1.0.8 is both the most expressive and the most robust version ever shipped.

✨ Headline Features

Feature What it does
🎬 Personified Move Animations Each of the 6 piece types has a unique motion characteristic via the Web Animations API — the pawn hesitates then darts, the knight traces an L-shape parabola, the queen glides in an elegant arc, the king steps with solemn weight. GPU-composited for sustained 120fps.
🔊 Personified Sound Effects A pure Web Audio API synth engine (zero audio files) gives every piece a matching timbre: pawn = triangle 3-stage, knight = sine sweep + ding, bishop = sawtooth + filter sweep, rook = square + noise + impact, queen = 3-freq harmony + LFO vibrato, king = bell partials + 4 footsteps.
📳 Personified Haptics Six dedicated haptic signatures (PAWN_MOVE → KING_MOVE) plus CASTLE and PROMOTION, each with its own throttle, amplitude, and temporal pattern. A single turn produces exactly one haptic (mutually-exclusive if/else if chain) — never a muddy double-buzz.
🌓 Light / Dark Theme Auto-switches with the system setting via dual-channel detection (Java UiModeManager + JS data-theme + CSS prefers-color-scheme). Light mode uses an elegant silver palette; dark mode keeps the warm brown-red + bright gold. The ♔/♚ king icon flips to match the on-board pieces.
🧠 Stockfish 18 dotprod The arm64-v8a-dotprod variant (ARMv8.6-A DOTPROD instructions for NN inference acceleration) — the strongest Stockfish build available for modern ARM devices.

🎬 The Six Piece Personalities

Every piece now has a coherent animation + sound + haptic triad designed to feel like that piece's "character."

Piece Motion Sound Haptic Duration
Pawn Timid — hesitate back, then dart forward Triangle wave, 3-stage Three micro-trembles (low) 260 ms
Knight Agile — L-shape parabolic jump Sine sweep + crisp ding Lift-off + landing (mid) 380 ms
Bishop Sharp — quick diagonal glide Sawtooth + filter sweep Smooth bell curve (mid-low) 270 ms
Rook Fierce — charge → dash → impact + light shake Square + noise + impact thud Charge + heavy impact (mid-high) 290 ms
Queen Elegant — graceful arc + heavy shake 3-freq harmony + LFO vibrato Massive slam (highest) 520 ms
King Solemn — heavy single step + heavy shake Bell partials + 4 footsteps Four solemn thuds (high) 560 ms

Engineering note: Every animation frame is a pure transform update — zero pixel ops. A single static filter: drop-shadow is cached on the composited layer, so the shadow travels with the piece "for free." Result: 120fps sustained even on mid-range devices.


🛡️ Robustness & Stability

v1.0.8 went through 5 independent first-principles code reviews (Phase 24, 28, 30, 46, 49), culminating in a 5-parallel-subagent audit of all ~27,000 lines. Every "避坑" (pitfall-avoidance) item is verified correct.

Bug fixes that mattered (Phase 49 highlights)

  • Chess960 king-capture misclassified as castling — a king capturing to column 6/2 (e.g. Kxg1 with the king on f1) was silently destroying pieces. Fixed with an empty-destination guard.
  • importPGN data-loss on malformed [FEN] — an invalid FEN tag destroyed all persisted review evals across all games and corrupted the Chess960 mode, even though the import failed. Fixed by validating startState before any side effects.
  • onHintMove applied to the wrong position — if you moved between requesting a hint and the engine responding, the stale hint landed on the new position. Fixed with an isHintLoading staleness guard mirroring onBestMove's pattern.
  • Worker-pool Blob URL leaknew Worker(url) failures leaked the Blob URL forever. Fixed by hoisting url and revoking on failure.
  • Heatmap cache-key collision — a 10-move variation and a 10-move mainline selection could produce identical cache keys → stale heatmap. Fixed with a content-based key.

The button-width saga (Phase 44 → 50)

A single UI bug — compact buttons stretching to full row width — survived seven fix attempts before the true root cause was found:

Phase Approach Why it failed
44 width: fit-content Ignored in flex containers
47 Inline flex: 0 0 auto Correct for flex items — but the buttons were grid items
48 .btn-compact + !important flex Same flex approach, same failure
49 Added .btn-compact to click selector Robustness, not the root cause
50 .btn-row opts out of the grid transform The real fix

Root cause: A portrait @media rule (.dlg-sec > div[style*="display:flex"]) was converting the button containers from display: flex to display: grid with grid-template-columns: 1fr auto — a layout designed for label+input rows. The buttons became grid items, and grid items ignore all flex properties. The fix: a .btn-row marker class + a higher-specificity override (0,3,1 > 0,2,1, later source order) that restores display: flex and neutralizes grid-template-columns.

Lesson: When flex properties have no visible effect, check whether the parent is secretly a CSS Grid.


🔧 Phase 51 — Three Final Fixes

After the button-width fix, three remaining issues were addressed:

1. PGN round-trip castling failure (HIGH)

Symptom: A game exported as PGN and re-imported would silently drop moves after any castling move (O-O / O-O-O). For example, a game ending 6.O-O O-O 7.Re1 a6 would import as 6.O-O O-O 7. _ a6 — the Re1 move vanished.

Root cause: pseudoMoves() attaches the castle flag to the to object ({row, col, castle}), and legalMoves() builds the full move as {from, to:{row,col,castle}, piece}. So mv.castle (top-level) is undefined for moves from legalMoves(). The UI path (executeMove) copies the flag to mv.castle before calling makeMv, but the PGN-replay path (_applySANMove) calls makeMvInPlace directly — mv.castle was undefined, so _castleSide() fell through to a heuristic that read the wrong board state. Result: castling only moved the king, not the rook. Subsequent rook moves failed to parse.

Fix: _castleSide() now checks both mv.castle (top-level, set by executeMove) and mv.to.castle (set by pseudoMoves). Verified: full Italian Game round-trips perfectly.

2. Move-classification label: "Book" → "Mediocre"

The review-mode move classification label for near-equal moves (eval delta < 50cp) was "Book"/"开局库". Changed to "Mediocre"/"平常" per user request — "Book" was misleading (it doesn't mean the move came from an opening book). The CSS class .book is unchanged.

3. Eval-chart dark-mode line invisibility

The review eval-trend chart's negative-eval line color (--chart-fill) was #1A1A2E (near-black) in dark mode — invisible against the #1a0a0a background. Changed to #5dade2 (light blue) for dark mode, clearly visible and hue-distinct from the positive-eval line (#E8E8F0 light cream). Light mode unchanged.


📱 Xiaomi HyperOS 3 Compatibility

Regalia is tested on Xiaomi HyperOS 3 and hardened against its aggressive process management:

  • Foreground EngineService keeps the Stockfish subprocess alive when the app goes to background
  • 5-second engine heartbeat detects zombie processes fast (OEM process killers are ruthless)
  • Dual-channel theme detectionprefers-color-scheme alone is unreliable on HyperOS 3, so we also read UiModeManager on the Java side and mirror it to a data-theme attribute
  • 6-step WebView destroy prevents render-process crash loops
  • onRenderProcessGone recovery
  • APK signed with v1 + v2 + v3 schemes — all three verified ✅

♟️ Full Feature List

Click to expand the complete feature list

Engine & Play

  • Stockfish 18arm64-v8a-dotprod native build (ARMv8.6-A DOTPROD for NN acceleration)
  • 8 difficulty levels — 800 ELO (beginner) → 2800+ ELO (max strength) + Skill Level mode
  • Chess960 / Fischer Random Chess — all 960 starting positions, proper castling rules, SP-ID selector, Shredder-FEN, UCI_Chess960 option
  • Ponder mode — engine thinks on your opponent's time
  • WDL display — Win/Draw/Loss probability alongside the eval
  • MultiPV analysis — 1–8 lines simultaneously

PGN & Analysis

  • Standardized PGN — strict 1994-spec import/export, Seven-Tag Roster, [%eval] / [%clk] / [%emt] annotations
  • NAG & visual annotations$1$19 NAGs; auto-cached [%csl] highlights + [%cal] arrows
  • Time-Control chess — Sudden Death / Fischer / Bronstein / US Delay, live clocks, auto-emit [%clk]
  • Review mode — full replay, eval trend chart, move classification (brilliant / good / blunder), engine eval cache wit...
Read more

Regalia v1.0.7

Choose a tag to compare

@YDW99 YDW99 released this 29 Jun 23:48
2a3fdbc

What's Changed

Full Changelog: v1.0.6...v1.0.7


The source code files include an outdated README.md file along with the manuals. For detailed information about the relevant revisions (which encompass the manuals), please refer to the following:

b4ef1e7


Regalia v1.0.7 — Stability & Performance Maintenance Release

versionCode 107 · versionName "1.0.7" · 2026.6.28 – 2026.6.30
Stockfish 18 (arm64-v8a-dotprod) · Android 5.0+ (API 21) · arm64-v8a only

v1.0.7 is a code-quality and stability maintenance release based on three independent code-review reports + two comprehensive first-principles code-review passes (Phase 18/19) + two rounds of UX refinement (Phase 20/21). Across 21 phases, 52+ improvements were implemented. No new features were introduced, but numerous latent bugs were fixed, and the two remaining architectural suggestions from the Kimi audit report (LRU cache eviction + virtual list) were implemented.


📊 Release Overview

Metric Value
Total Phases 21
Fixes / Optimizations 52+
Code-Review Subagents 7 (Phase 19)
Lines Reviewed 28,281
Critical Bug Fixes 8
Performance Breakthroughs 3
Compatibility Xiaomi HyperOS 3 ✅

🎯 Key Highlights

⚡ Performance Breakthroughs

1. _reviewEvalCache LRU Eviction (Phase 18)

The old code commented "Unlimited cache size", arguing 60k entries ≈ 12MB was "negligible". First-principles analysis found three errors:

Old Claim                          Actual Reality
─────────────────────────────  ─────────────────────────────
~200B per entry                ~400B (JSON persistence overhead)
60k entries ≈ 12MB             ≈ 24MB JSON
"negligible" main-thread block JSON.stringify blocks 100-300ms
localStorage quota ample       WebView ~5MB → QuotaExceededError silently swallowed

Fix:

  • MAX_ENTRIES = 2000 soft cap (~800KB JSON, well under any quota)
  • _evictIfOverCap() evicts oldest entries by Map insertion order (= LRU order)
  • get()/set() refresh LRU order via delete+set
  • Does NOT evict _reviewEvalRequestedStep (the in-flight step) to avoid "analyzing..." flicker
  • Persistence preserves LRU order via Array.from(m.entries())
  • Backward-compatible: persisted files >2000 entries auto-evict on next set()

2. Review Move-List Virtual List (Phase 18)

Threshold: 80 moves    Overscan: 10 rows    Debounce: 80ms

When move count > 80, windowed rendering is enabled — only the viewport + 10 rows of overscan above/below are rendered as DOM nodes; top/bottom spacer <div>s fill the total scroll height.

Feature Implementation
Shared render function _buildReviewMovesInnerHTML(start, end) eliminates ~40 lines of landscape/portrait duplication
Scroll listener Passive (passive:true), cannot block scrolling or interfere with scroll-restore
Partial refresh _refreshReviewMovesOnly() replaces only .review-moves innerHTML
Dynamic row height First render measures average height of first 10 rows via requestAnimationFrame
Window forcing _forceReviewWindowToStep() ensures reviewGoTo() target is in window
State reset _resetRvVirtualState() on enter/exit review, new game, import

Performance: Not enabled under 80 moves (full render is faster for short lists); long-game render cost reduced from O(total moves) to O(visible + overscan).

3. Move Animation Slowed 30% (Phase 20, 120fps Preserved)

Piece     Old Duration    New Duration
─────────────────────────────────────────
Pawn      180ms           240ms
Knight    240ms           320ms
Bishop    210ms           280ms
Rook      180ms           240ms
Queen     260ms           340ms
King      210ms           280ms
  • 120fps high-frame-rate preserved — GPU-composited via will-change:transform + translate3d
  • cubic-bezier(.25,.1,.25,1) easing curve unchanged; only duration is longer
  • CSS transition durations updated to match JS durations table

🐛 Critical Bug Fixes

Chess960 Castling "King Self-Capture" Fatal Bug (Phase 17)

Symptom: In Chess960 mode, when the king's starting position happens to be its castling destination square (e.g. an SP-ID where the white king starts on g1), castling caused the king to "capture itself" and vanish from the board.

Root cause: The makeMv/makeMvInPlace castling branch unconditionally executed:

ns.board[to.row][to.col] = ns.board[from.row][from.col];  // self-copy
ns.board[from.row][from.col] = null;                       // ← BUG: when from===to, this nulled the king's own square

Fix: When castling AND from === to, skip the king move entirely (per the Fischer Random Chess "castling in place" rule). Also fixed unmakeMv and animateMove symmetrically. Added 20 Node-sandbox regression tests.

Chess960 unmakeMv Board Corruption (Phase 18, Critical)

Symptom: In ~half of all Chess960 SP-ID positions (e.g. white king on f1 castling kingside), the makeMvInPlaceunmakeMv round-trip corrupted the board.

Root cause: unmakeMv step 2 restores the king at f (king source), but the rook's destination cr.to may equal f (the rook moved to the king's old square) — step 2 already overwrote the rook with the king; step 4 s.board[cr.from] = s.board[cr.to] then read the king instead of the rook.

Fix: Save the rook piece itself in undo.castlingRook.piece, restoring from the saved piece rather than relying on board state.

_reviewEvalCache Corruption Race (Phase 19, Critical)

Symptom: requestEngineEval's cache-hit fast path and terminal-position fast path did not clear the pending debounce timer, allowing a stale debounce timer capturing a prior step's FEN to fire after the cache-hit return and overwrite the current step's correct cached eval.

Fix: Clear _reviewEvalDebounceTimer and increment _evalStaleGen on both fast paths to invalidate any in-flight callback.

Cross-Mode Stale Callback Race (Phase 19, Critical)

Symptom: onEngineEval's two filters are mode-exclusive; after exitReview, a review-mode callback still in flight could pass the normal-mode gen check and overwrite the correct eval for up to 30 seconds.

Fix: Capture _evalRequestReviewMode at request time; onEngineEval rejects cross-mode callbacks at the top.

PGN [%eval] Placeholder Offset (Phase 19, Critical)

Symptom: FEN-start PGNs where Black is to move had evals attach to the wrong reviewStep (off by 1).

Root cause: _parsePGN was unaware of the null placeholder that importPGN prepends after parsing.

Fix: Apply _placeholderOffset when populating the eval/annotation/comment caches.

StockfishNative Thread Safety (Phase 19)

Five fields changed from non-volatile to volatile:

private volatile Process engineProcess;
private volatile BufferedReader engineReader;
private volatile OutputStreamWriter engineWriter;
private volatile Thread readerThread;
private volatile ExecutorService _engineExecutor;

Root cause: These fields are written from multiple threads (_engineExecutor worker, heartbeat thread, JS binder) without volatile/synchronization. A read on one thread may never observe a null write from another, producing NPEs or writes to a destroyed process's stream.

stopAndWaitForBestmove Timeout Stale-Bestmove Corruption (Phase 19)

Symptom: When the engine didn't respond to "stop" within 1 second, the late bestmove was processed as a real move, potentially generating an AI move for the wrong position.

Fix: On timeout, set _discardingPonderBestmove = true to discard the late bestmove.

ChessApp UncaughtExceptionHandler Swallowed Error (Phase 19)

Symptom: Worker-thread Errors (OOM/StackOverflow/NoClassDefFoundError) were silently swallowed, leaving the JVM in a corrupted state — e.g. if the SF-Reader thread died from OOM, the engine silently stopped responding while isProcessAlive() still returned true, causing a 30-120s apparent hang.

Fix: Error subclasses are now chained to the default handler so the process dies cleanly and restarts.


🎨 UX Refinements (Phase 20 + 21)

Review Eval Bar / Analyze Button Enlarged (Phase 20)

Element Old New
Eval bar font (portrait/landscape) .75rem / .7rem .85rem / .8rem
Emoji size .95rem 1.1rem / 1.05rem
max-height 1.9em 2.4em
padding 3px 8px 5px 10px
Analyze button font .7rem .8rem
Analyze button min-height 28px 34px

Text and emoji now display fully with comfortable breathing room — no longer cramped.

Portrait Dialog Positioning Precision Fix (Phase 21)

Phase 20 attemptPhase 21 precision fix:

Phase 20: padding-top:18vh + padding-bottom:10px
          → center = 18 + (100-18-1)/2 ≈ 59vh  ❌ BELOW center

Phase 21: padding-top:10px + padding-bottom:12vh
          → center = 10px + (100vh-10px-12vh)/2 ≈ 44vh  ✅ slightly above center

The fullscreen dialog (New Game Settings) uses negative margins to cancel both paddings, maintaining true full-screen fill.

Landscape Anti-Shake Clipping Root-Cause Fix (Phase 21)

Phase 20 attempt (.bsec margin-right:10px) was ineffective — the clip happens at .bsec's own overflow:hidden boundary, not at the gap.

Phase 21 triple fix:

  1. Rolled back margin-right:10px
  2. .bsec changed to overflow-x:visible; overflow-y:hidden
  3. _antiShake now reserves 8px in BOTH orientation...
Read more

Regalia v1.0.6

Choose a tag to compare

@YDW99 YDW99 released this 27 Jun 15:03
9ac7f6b

What's Changed

Full Changelog: v1.0.5...v1.0.6


Regalia v1.0.6 — Release Notes

Version: v1.0.6 (versionCode 106)
Engine: Stockfish 18 arm64-v8a-dotprod
Platform: Android 5.0+ (API 21) · ARM64 · Xiaomi HyperOS 3 compatible
License: AGPL v3 (application) + GPL v3 (DroidFish-derived code & Stockfish engine)


📥 Download

File Description
Regalia-v1.0.6-release.apk Release APK (v1 + v2 + v3 signed)
Regalia-v1.0.6-manual-zh.html Chinese user manual
Regalia-v1.0.6-manual-en.html English user manual

⚠️ Enable "Install from unknown sources" in system settings before installing.


🆕 What's New in v1.0.6

v1.0.6 is a feature-enhancement release with 10 major new features and 4 rounds of bug fixes (including critical Chess960 castling fixes).

♟️ Chess960 Improvements

ECO Opening Recognition Suppression

Chess960 (Fischer Random Chess) has no fixed opening theory. When Chess960 mode is active, the in-game ECO Opening info panel and Opening Recommendation bar are now fully suppressed — no more meaningless ECO lookups for non-standard start positions.

// game-logic.js — _startGameImpl()
_ecoEnabled = !(typeof dlgChess960 !== 'undefined' && dlgChess960);

// ui.js — ECO panels gated by gameVariant check
if (_ecoEnabled && !(gameVariant === 'chess960')) { /* show ECO panel */ }

Chess960 Castling Detection Overhaul

Replaced the legacy Math.abs(to.col - from.col) === 2 pattern (which only works for standard chess) with an explicit castle flag on castling moves plus a unified _castleSide() helper:

Detection Method Standard Chess Chess960 (1-col king move)
Legacy === 2
mv.castle flag
Fallback (dest col + distance ≥ 2) ✅ (2+ col moves)

Files updated: pseudoMoves(), makeMv(), makeMvInPlace(), moveAlg(), animateMove(), _uciToSimple(), tablebase.js applySANMove(), stats.html executeMove()


🏰 King-then-Rook Castling Gesture

A new castling operation method — essential for Chess960 where some positions can only be castled this way.

How it works:

  1. Select a king that can castle
  2. The castling-capable rook(s) are visually marked with a golden dashed ring (with pulse animation)
  3. Click the marked rook → castling executes instantly
  ┌───┬───┬───┬───┬───┬───┬───┬───┐
  │ ♖ │   │   │ ♔ │   │   │   │ ♖ │   ← Standard: king e1, rooks a1/h1
  └───┴───┴───┴───┴───┴───┴───┴───┘
                  └──┐
                     ▼  Select king → rook gets golden ring
  ┌───┬───┬───┬───┬───┬───┬───┬───┐
  │ ♖ │   │   │ ╔═╗ │   │   │ ╔═╗ │ ♖ │   ← Click the ringed rook
  └───┴───┴───┴───┴═╤═╩───┴───┴═╤═┘
                    └─ king→g1, rook→f1

Why it matters: In Chess960, when the rook's source square is the king's destination (e.g. rook on g1, king castles kingside to g1), the traditional "click the king's destination" gesture is ambiguous. The king-then-rook gesture resolves this.

🔧 Implementation Details
  • _computeCastlingRookSetForSelection(selPos, moves) — pure helper, no global state mutation
  • _updateChangedSquares() — lightweight update path now syncs the marker (was only in full render)
  • castle-ring CSS — golden dashed border + castlePulse keyframe animation
  • _getCastlingRookForClick() — click handler detects rook clicks and triggers castling

📊 Stats Page Per-Move Selection

Click any move or the initial FEN in the stats page's PGN Text panel to instantly view:

  • The board position after that move (or the initial position)
  • All statistics computed up to that move only — not including the effect of later moves
  • Material balance, phase, pawn structure, eval trend, move quality distribution

When nothing is selected, the final complete statistics are shown.


🔄 PGN [SetUp]/[FEN] Round-Trip Preservation

Before: Importing a PGN with [SetUp "1"] and [FEN "..."] headers, then exporting, would drop both headers.

After: importPGN() now correctly assigns result.startFEN to _setupFEN, so _buildPGNString() regenerates both headers on export.

[SetUp "1"]
[FEN "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"]

🎨 Unified Gray-Out Styling

When a toggle or stepper is grayed out (disabled), only the control itself is dimmed. Surrounding labels, descriptions, and explanations remain at full opacity.

Element Before After
Toggle switch Dimmed Dimmed
Section heading Dimmed Full opacity
Description text Dimmed Full opacity
Explanation paragraph Dimmed Full opacity
Gray-out reason Absent New (red, same color as Engine Config warning)

New gray-out explanations:

  • "Chess960 is unavailable while AI Opening Book is enabled"
  • "AI Opening Book is unavailable while Chess960 is enabled"

📱 Portrait-Optimized New Game Dialog

The New Game Settings dialog now has dedicated portrait-mode CSS while preserving the landscape layout:

Property Landscape Portrait (<900px) Narrow Portrait (<480px)
Max width 500px 460px 96%
Padding 24px 18px 16px 14px 12px
Time control input 80px 80px 70px
ECO search row Inline Wrap Stack vertically

📜 Scroll-Position Preservation

Fixed a bug where scrollable windows would suddenly jump back to the top after a state change triggered a re-render.

Affected containers:

  • .dlg — dialogs (New Game, Engine Config, Import, About)
  • .panel — right-side info panel (landscape)
  • .review-moves — review mode move list
  • .op-list — opening selector
  • stats.html #content — full document scroll

Mechanism: _savedContainerScrolls array captures scrollTop before app.innerHTML = h and restores it after (clamped to new scrollHeight).


⚙️ Engine Evaluation FEN Sanitization

Added _sanitizeFenForEngine() — strips inconsistent castling rights before sending to Stockfish.

Problem: The FEN 4k3/8/8/8/8/8/8/r1K4R w q - 1 3 has black queenside castling right (q) but no black rook on a8. Stockfish would enter a degraded state — no info lines, no bestmove, hanging until the 30s safety timer fired, then retrying indefinitely.

Solution:

function _sanitizeFenForEngine(fen) {
  // Parse board, validate each castling right against actual piece placement
  // K → white king on e1 AND white rook on h1
  // Q → white king on e1 AND white rook on a1
  // k → black king on e8 AND black rook on h8
  // q → black king on e8 AND black rook on a8
  // Shredder-FEN (Chess960) is passed through unchanged
}

Applied to all engineEval, engineEvalDeep, engineHint, engineGo, engineGoTimed, and startPonder calls.


🏅 SL Mode Skill-Level Display

When the AI opponent is in SL (Skill Level) mode, the actual skill level value is now shown:

Location Before After
AI opponent bar SL SL20
Top toolbar button SL SL20
PGN [Black ""] tag AI Opponent SL AI Opponent SL20

New Java bridge method: getEngineSkillLevel() returns the current engineSkillLevel int.


🐛 Bug Fixes

Round 1 — Initial v1.0.6 Release

  • White screen on launch — A backtick in a comment inside the _WORKER_SOURCE template literal (worker-pool.js) prematurely terminated the string, causing a SyntaxError that blocked the entire script. Fixed by removing the backtick.
  • executeMove() lost castle flag — The mv object was built as {from, to, piece, promotion} without copying castle from legalMvs, breaking Chess960 castling detection. Fixed by looking up the matching legal move.
  • _computeAndCacheVisualAnnotations() replay path — Same castle flag loss during move replay. Fixed by restoring from moveRecords.isCastling.

Round 2 — Castling Root Cause

  • executeMove() castle flag propagation — Comprehensive fix ensuring castle flag flows from legalMvsmvmakeMv()/moveAlg()/_castleSide().
  • _castleSide() fallback logic — Improved to use destination column + distance (≥2 cols) instead of just === 2, covering 2+ col Chess960 castling.
  • _uciToSimple() Chess960 detection — Simplified to distance-based (≥2 cols) to avoid 1-col ambiguity with normal king moves.
  • _sanitizeFenForEngine() Shredder detection — Simplified from double-regex to single /^[KQkq-]+$/.
  • _finishAnim() cleanup — Now clears _lastAnimMv to avoid stale references.

Round 3 — Chess960 Rook Capture & Analyze-All Off-by-One

  • Chess960 own rook captured during castling — In Chess960, when the rook's source square equals the king's destination (e.g. rook on g1, king castles kingside to g1), makeMv()/makeMvInPlace() moved the king first (overwriting the rook), then tried to move the rook — but it was already gone.

    Fix: Detect castling before moving the king, save the rook piece reference, move the king, then place the saved rook at its destination. Also fixed stats.html executeMove() (review mode replay).

    Test verification
    Before: King e1, Rook g1 (Chess960)
    Kingside castling: king → g1, rook → f...
    
Read more