Skip to content

Regalia v1.0.9

Choose a tag to compare

@YDW99 YDW99 released this 02 Jul 20:23
· 23 commits to main since this release
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 annotations!

The variation's tags accumulated into the pending per-move payload. When the next main-line move (e5) was seen, the payload was flushed and attached to e5 — corrupting e5's annotations with variation-internal data.

Fix

Gate extraction of position-specific tags on _depth === 0:

if (_depth === 0) {
  // Extract [%eval], [%csl], [%cal] — these describe the MAIN-LINE position only.
  // …existing extraction logic…
}
// Free-text comments are still extracted at ALL depths (with "[var] " prefix
// for variation comments), so variation commentary remains visible in the
// move-list comment display.

Why free-text is allowed at all depths: Free-text comments are display-only (they appear in the move-list comment popup). They don't claim to describe a position's tactical state. Position-specific tags ([%eval]/[%csl]/[%cal]) are different — they are assertions about a specific board state, and attaching them to the wrong position is a correctness bug.

4. Imported PGN moves lacked isCheck / isCastling

Symptom

For PGN files loaded via Import PGN, the red check-path arrows and green king-escape arrows never appeared — even for moves with + or # in the notation. Live-played games showed them correctly.

Root cause

The importPGN loop built moveRecords with {notation, from, to, piece, captured, promotion, time, variations} — but omitted isCheck and isCastling. The annotation generator (_computeAndCacheVisualAnnotations) checks moveRecords[moveIdx].isCheck to decide whether to draw red/green arrows. For imported moves, this was undefined → falsy → no arrows.

Fix

importPGN now computes both fields from the replayed state, mirroring the live-play executeMove logic:

const _oppColor = replayState.currentTurn;
const _oppKing = _oppColor === 'white' ? replayState.wk : replayState.bk;
const _isCheck = _oppKing ? inCheck(replayState.board, _oppColor, _oppKing) : false;
const _isCastling = !!(typeof _castleSide === 'function'
                      && parsedMove && parsedMove.move
                      && _castleSide(parsedMove.move, preMoveState));
moveRecords.push({
  // …existing fields…
  isCheck: _isCheck,
  isCastling: _isCastling,
  // …
});

Result: Imported games now receive the same visual-annotation treatment as live-played games. Red check arrows and green escape arrows appear correctly for every check/checkmate in the PGN.


🛡️ Robustness Hardening

5. stats.html executeMove now clears castling rights

Previously, stats.html's executeMove updated newState.wk/bk but never cleared newState.castlingRights when the king or a rook moved, or when a rook was captured. This meant downstream buildSAN / state serialization could emit stale KQkq markers for a state where castling was no longer legal.

The fix mirrors the main app's game-logic.js makeMv/makeMvInPlace behavior:

  • King moves → clear both castling rights for that color.
  • Rook moves from its home square (col 0 or 7 on its home row) → clear the corresponding side's right.
  • Rook captured on its home square → clear the corresponding side's right.

This is a latent correctness issue (the stats page is display-only, so stale rights wouldn't corrupt gameplay), but it ensures the stats page's FEN/SAN output is always spec-compliant.


🎨 Eval-Chart Palette Unified to Blue-vs-Red (both dark & light modes)

6. One consistent color language, tuned per theme

Before

Mode --chart-line (positive eval) --chart-fill (negative eval) Problem
Dark #E8E8F0 (near-white) #5dade2 (light blue) Both cool/light hues — insufficient differentiation. Light blue clashed with the warm-brown-gold theme.
Light #4a4a52 (dark gray) #2c2c34 (very dark gray) Both dark grays — nearly indistinguishable. Users couldn't tell White-advantage from Black-advantage.

After — unified blue-vs-red convention

Mode --chart-line (White advantage) --chart-fill (Black advantage) --chart-critical (current marker)
Dark #5dade2 (sky blue, brighter) #e74c3c (warm red, brighter) #ffd700 (gold)
Light #2c5f8d (steel blue, deeper) #c0392b (deep red, deeper) #d4a017 (gold)

Plus --chart-grid and --chart-axis retuned to warm-brown shades (#4a3020, #8a6a3a) in dark mode for palette harmony.

Design rationale

  1. Blue vs red = opposite ends of the color wheel → maximally hue-differentiated. No more "which line is which?"
  2. Universal chess-software convention — blue = good/White-favorable, red = bad/Black-favorable. Users instinctively understand it.
  3. Per-mode saturation tuning — dark mode uses brighter, more saturated shades for visibility on #1a0a0a; light mode uses deeper, muted shades for contrast on #f0f0f3.
  4. Palette harmony — dark-mode grid/axis now use warm browns that blend with the existing warm-brown-gold theme, instead of cold grays that fought it.

Companion fix — data-point outlines

The data-point outline was hardcoded to two rgba values that washed out in light mode:

// BEFORE — light-cream outline invisible on light background
strokeColor = 'rgba(255,230,150,0.85)'; // for negative-eval points

// AFTER — theme-aware via CSS variable
strokeColor = _C_STROKE; // = --chart-text-stroke (dark in light mode, light in dark mode)

📊 Impact Summary

Category Count Severity Breakdown
Critical bug fixes 2 PGN import failure + review/stats board corruption
High-severity annotation fixes 2 Variation contamination + missing isCheck/isCastling on import
Robustness hardening 1 stats.html castling-rights clearing
Visual-design unification 1 Eval-chart palette unified blue-vs-red (both modes)
Total changes 6

Files changed

src/main/assets/chess.src/tablebase.js      # PGN tag + brace fix, variation isolation, isCheck/isCastling import
src/main/assets/chess.src/game-logic.js     # _castleSide local-state parameter
src/main/assets/chess.src/index.html.tpl    # chart palette unified (both modes)
src/main/assets/chess.src/ui.js             # chart point outline theme-aware + fallback colors
src/main/assets/stats.html                  # PGN fix, castling detection fix, castling-rights clearing
src/main/assets/chess.html                  # rebuilt from chess.src/ via build-chess.py
build.gradle                                # versionCode 108 → 109, versionName "1.0.8" → "1.0.9"
src/main/res/values/strings.xml             # app_name "Regalia v1.0.8" → "Regalia v1.0.9"
src/main/java/com/Regalia/MainActivity.java # VERSION "v1.0.8" → "v1.0.9"
src/main/java/com/Regalia/StockfishNative.java # ENGINE_VERSION "v1.0.8" → "v1.0.9"
src/main/java/com/Regalia/ChessApp.java     # init log "v1.0.8" → "v1.0.9"
src/main/java/com/Regalia/ChessWebViewClient.java # Version comment "v1.0.8" → "v1.0.9"

🧪 Verification

All fixes were validated with purpose-built Node.js test harnesses (kept under scripts/ in the source tree):

Test What it verifies Result
test-pgn-parse.js PGN 2Kbug.pgn (single-line, 71 half-moves, 11 variations) imports all 71 moves ✅ 71/71 moves, 10 variations, no extra kings
test-review-kings.js Full importPGN + review-replay flow produces no extra kings at any step ✅ No extra kings at any of 71 steps
test-variation-annotations.js Variation comments don't contaminate main-line annotations ✅ No variation [%csl]/[%cal]/[%eval] leaked to main line
test-ischeck-iscastling.js Imported moves have correct isCheck (for Bxf7+) and isCastling (for O-O) Bxf7+isCheck=true; both O-OisCastling=true

Manual verification checklist

  • APK installs on Xiaomi HyperOS 3 (v1+v2+v3 signature verified)
  • PGN 2Kbug.pgn imports all 71 half-moves with 11 variations
  • Review board shows no extra kings at any step
  • Main-line annotations are not contaminated by variation comments
  • Red check arrows + green escape arrows appear for imported PGNs
  • Eval chart colors are clearly distinguishable in both dark and light modes
  • Data-point outlines are visible against both backgrounds

📥 Installation

Option A — Direct APK install

  1. Download Regalia-v1.0.9-release.apk.
  2. On your Android device: Settings → Security → Unknown sources (enable).
  3. Open the APK (e.g., from Files → tap to install).
  4. First launch extracts the Stockfish 18 engine binary (~25 seconds, progress bar shown).

Option B — Build from source

# 1. Prerequisites
export JAVA_HOME=/path/to/jdk21
export ANDROID_HOME=/path/to/android-sdk
# SDK packages needed: platform-tools, build-tools;34.0.0, platforms;android-35,
#                       ndk;27.2.12479018, cmake;3.22.1

# 2. Download Stockfish 18 engine binary
curl -L -o sf.tar https://github.com/official-stockfish/Stockfish/releases/download/sf_18/stockfish-android-armv8-dotprod.tar
tar -xf sf.tar
mkdir -p src/main/jniLibs/arm64-v8a
cp stockfish/stockfish-android-armv8-dotprod src/main/jniLibs/arm64-v8a/libstockfish.so
chmod +x src/main/jniLibs/arm64-v8a/libstockfish.so

# 3. Build chess.html from source modules
python3 build-chess.py

# 4. Build the APK
./gradlew assembleRelease --no-daemon --console=plain
# Output: build/outputs/apk/release/Regalia-release.apk

# 5. Verify signature
$ANDROID_HOME/build-tools/34.0.0/apksigner verify --verbose Regalia-release.apk

See BUILDING.md in the source tarball for full details, including the zip-timestamp-normalization workaround for CMake/ninja.


🔒 Privacy & Permissions

Regalia is a fully offline chess app. No account, no network, no tracking.

Permission Why Data collected
VIBRATE Personified per-piece haptic feedback (v1.0.8+) None
FOREGROUND_SERVICE Engine stability (prevents OS killing the engine during analysis) None
POST_NOTIFICATIONS (API 33+) Foreground-service notification None
READ_EXTERNAL_STORAGE / SAF PGN file import (user-initiated) Only files the user selects

Network: The only network access is an optional Lichess Syzygy tablebase query (endgame lookup), which can be disabled in Settings. No telemetry, no analytics, no crash reporting to third parties.

See PRIVACY.md in the source tree for the full policy.


📜 License & Attribution

Regalia is a combined work under dual licensing:

  • AGPL v3 — the application as a whole (original Regalia work)
  • GPL v3 — code derived from DroidFish and the Stockfish engine itself

Per GPL v3 Section 13, these licenses are compatible for combination. Each component retains its original license. Since AGPL v3 imposes stricter requirements (including network-interaction provisions under Section 13), its obligations effectively extend to the entire combined work.

Component License Source
Regalia original code (Java/JS/CSS/build) AGPL v3 This repository
DroidFish-derived code (engine mgmt, PGN, move gen) GPL v3 DroidFish by Peter Österlund
Stockfish 18 engine binary GPL v3 Stockfish

See LICENSE-AGPL v3, LICENSE-GPL v3, LICENSE-Apache v2.0, NOTICE, NOTICE-DroidFish, and AUTHORS-stockfish in the source tree for full texts.


🗺️ Roadmap

v1.0.9 closes the known correctness issues reported against v1.0.8. The next release (v1.0.10+) will focus on:

  • Performance — further reduce per-render allocations in the review eval-chart hot path
  • Chess960 PGN export — Shredder-FEN castling notation is already correct on import; export edge cases for SP-IDs with bishops on corners need hardening
  • Accessibility — TalkBack screen-reader support for the board grid and move list
  • Engine — evaluate Stockfish 18.1 when released (NNUE net improvements)

Have a bug to report or a feature to request? Open an issue on the GitHub issue tracker. Pull requests welcome — please read BUILDING.md first.


📋 Changelog Archive

v1.0.8 (Phase 22–51, 2026.6.30 – 2026.7.2)Personified animation & sound + light mode + 30-phase stability saga

Complete redesign of the move animation and sound effect system per the "Personified Chess Move Animation" and "Personified Chess Sound Effects" reference documents. Each piece has a unique personified motion characteristic and matching timbre. Light mode support added following Android Dark/Light Theme Design Principles.

Phase 51 — PGN round-trip castling fix (_castleSide checks mv.to.castle), move-classification label change ("Book" → "Mediocre"), eval-chart dark-mode line visibility fix.

Phase 50 — Button-width TRUE root-cause fix: .btn-row opts out of portrait grid transform.

Phase 49 — Comprehensive first-principles re-review: 6 bugs + 12 robustness issues + compliance reconciliation.

Phase 38–48 — PGN cache UI layout, castle-rights-loss root-cause fixes, button-width root-cause fix (.btn-compact CSS class).

Phase 29 — Setup-mode ⚡ button normalization, castling sound/haptic redesign, light-mode contrast fixes, WebView robustness.

Phase 22–28 — Full move-animation & sound redesign, Stockfish 18 dotprod engine integration, Web Worker multithreading, six-piece haptic personality design, comprehensive first-principles code review.

v1.0.7 (2026.6.28)Code-quality & stability maintenance

Fixed several latent bugs uncovered by three independent code reviews (critical-move cache not invalidated on undo, lightweight board update path missing the castling-rook marker, engine-notification throttle cache staleness, stats-page PGN comment XSS risk, cross-game eval-cache pollution, CSP potentially blocking Blob Workers). Added clickable GitHub repository links for DroidFish and Stockfish in the About dialog. Refined portrait full-screen layout and Android back-button handling. Merged duplicated HTML-escape functions.

Phase 2 — Main-screen "Quick Toolbar" (Undo/Redo/Flip/AI Hint/Control Range below the board), setup-mode 🔁 manual castle-rights marker + ⚡ manual en-passant marker.
Phase 17 — Chess960 castling "king self-capture" critical bug fix.
Phase 18_reviewEvalCache LRU eviction + review move-list virtual list.
Phase 19 — Comprehensive 7-subagent 28k-line code review + 20+ critical fixes.

v1.0.6 (2026.6.27)Chess960 ECO + PGN FEN round-trip

Chess960-mode ECO opening recognition suppression, PGN with FEN header round-trip preservation, per-move selection on the stats page, unified gray-out styling, portrait New Game dialog optimization, scroll-position preservation, engine-eval FEN sanitization, king-then-rook castling gesture (essential for Chess960), Chess960 castling detection overhaul, explicit SL skill-level display.

v1.0.5 (2026.6.27)Anti-shake + screen adaptation

Sensor-fusion board anti-shake (OIS principle), high aspect-ratio screen adaptation, notch/cutout/R-corner adaptation, phase analysis precision (opening/middlegame/endgame multi-criteria detection), review arrow shrink optimization.

v1.0.4 (2026.6.26)Chess960 + Time Control + standardized PGN

Fischer Random Chess (Chess960) mode, Time Control, standardized PGN import/export (compliant with the 1994 PGN specification), heatmap-control-based statistics, Seldepth (selective depth) display, visual annotations (NAG & [%csl]/[%cal]), Web Worker parallel computation, PGN cache manager, player renaming, bilingual Chinese/English UI, resign (🏳️ Resign).


💎 Acknowledgments

  • Stockfish — the world's strongest chess engine, developed by the Stockfish developers. Regalia ships with Stockfish 18 (arm64-v8a-dotprod build).
  • DroidFish by Peter Österlund — the Android chess app that Regalia's engine management, PGN parsing, and move-generation logic derive from.
  • Lichess — for the Syzygy tablebase API and the [%csl]/[%cal]/[%eval] PGN annotation conventions.
  • The Regalia beta testers who reported the PGN 2Kbug.pgn single-line parse failure and the "extra kings" review-board corruption — your detailed repro files made the root-cause analysis tractable.

Regalia v1.0.9Play offline. Analyze deeply. No account, no network, no tracking.

Built with Stockfish 18 · AGPL v3 · Compatible with Android 5.0–15 & Xiaomi HyperOS 3


AI-GEN