Skip to content

Regalia v1.0.6

Choose a tag to compare

@YDW99 YDW99 released this 27 Jun 15:03
· 48 commits to main since this release
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 → f1
    
    makeMv() result:
      g1 (col 6): ♔ (king) ✅
      f1 (col 5): ♖ (rook) ✅
      e1 (col 4): empty ✅
    
  • "Analysis complete" never displayed_reviewEvalCache is keyed by reviewStep (1-based), so cache size = moveRecords.length. But the check compared _reviewEvalCache.size >= reviewStates.length, and reviewStates.length = moveRecords.length + 1 (includes initial position at index 0). This off-by-one meant the comparison was always N >= N+1 → false.

    Fix: Three coordinated changes:

    • Analyze button: _totalSteps = moveRecords.length
    • reviewAnalyzeAll(): iterate 1..moveRecords.length
    • _reviewAnalyzeAdvance(): terminate when nextStep > moveRecords.length

Round 4 — Castling Rook Marker Display

  • Marker didn't appear on king selection_updateChangedSquares() (the lightweight update path) didn't include castle-ring marker logic — it was only in renderInternal().
  • Marker got stuck after appearing — Same root cause: the lightweight path didn't remove the marker when selection changed.
  • Fix: New pure helper _computeCastlingRookSetForSelection() computes the set without mutating globals. _updateChangedSquares() now computes both current and previous sets, includes both in the update list, and correctly adds/removes the castle-rook class + castle-ring div.

⚡ Performance Optimizations

  • Dead code removal_getCastlingRookSquares() removed (40 lines). It was redundant after renderInternal switched to _computeCastlingRookSetForSelection directly, and contained a wasteful double-computation of chess960CastlingRookMove().
  • _updateChangedSquares() castling-set computation — Eliminated legalMoves() recomputation for the previous selection by deriving castling rooks from the already-tracked _prevLegalSet (Set of row*8+col keys). If the previous piece was a king and _prevLegalSet contains col 6 or col 2, castling was legal — no need to re-run the full move generator.
  • Zobrist hash verification — Confirmed correct for the Chess960 edge case where rook source === king destination (XOR commutativity ensures correctness regardless of operation order).

🏗️ Technical Architecture

Regalia v1.0.6
├── Java Layer (Android Native)
│   ├── MainActivity.java          — WebView host, immersive mode, lifecycle
│   ├── StabilizationHelper.java   — Sensor-fusion board anti-shake (v1.0.5)
│   ├── StockfishNative.java       — Engine process, UCI, PGN cache, getEngineSkillLevel() (v1.0.6)
│   ├── StatsActivity.java         — Statistics page fullscreen WebView
│   ├── EngineService.java         — Foreground service (engine stability)
│   ├── ChessApp.java              — Crash protection
│   ├── TlsSecurityHelper.java     — TLS 1.2+ enforcement
│   └── RootDetector.java          — Informational root detection
├── JS Layer (WebView)
│   ├── game-logic.js              — Chess rules, _castleSide(), makeMv rook-save fix
│   ├── chess960.js                — SP-ID, Shredder-FEN, 960 castling helpers
│   ├── pgn-standard.js            — Standardized PGN codec, NAG, [%csl]/[%cal]
│   ├── worker-pool.js             — Web Worker pool (PGN/stats/control-map)
│   ├── ai-bridge.js               — Engine comms, _sanitizeFenForEngine(), SL display
│   ├── tablebase.js               — Syzygy tablebase + PGN import (FEN round-trip fix)
│   ├── ui.js                      — Rendering, castling gesture, scroll preservation
│   ├── eco-data.js                — ECO opening data
│   └── index.html.tpl             — CSS (portrait dialog, castle-ring animation)
├── C++ Layer
│   └── engine_jni.cpp             — JNI chmod/renice (from DroidFish)
└── Engine
    └── Stockfish 18 arm64-v8a-dotprod (NEON + dotprod)

📦 Deliverables

Artifact Size Description
Regalia-v1.0.6-release.apk ~77 MB Signed release APK (v1+v2+v3)
Regalia-v1.0.6-manual-zh.html ~382 KB Chinese manual (with v1.0.6 diagrams)
Regalia-v1.0.6-manual-en.html ~466 KB English manual (synced)

🔄 Upgrading from v1.0.5

v1.0.6 is fully compatible with v1.0.5's PGN archives and evaluation caches. Install directly over the existing app — no uninstall needed.

📂 PGN cache archives and evaluation caches are preserved across the upgrade.


📜 License

Component License
Original application code (UI, WebView, services, build scripts) AGPL v3
DroidFish-derived code (engine management, game logic, PGN parsing, UI patterns) GPL v3
Stockfish 18 engine binary GPL v3
ECO opening data CC0 (data) / AGPL v3 (code)
App icon AI-generated / AGPL v3

GPL v3 Section 13 permits this combination of licenses. AGPL v3's network interaction provisions (Section 13) apply to the entire combined work.


🙏 Acknowledgments


📌 Known Limitations

  • Syzygy Tablebase — Requires network connection; automatically disabled offline.
  • Chess960 castling — Fully supported in v1.0.6 (previous off-by-one and rook-capture bugs fixed).

Regalia uses "Regalia" solely as a project name. No trademark rights are claimed. Anyone is free to fork and rename their own version.


AI-generated