Regalia v1.1.0
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)
🎯 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-1or sum ≤ 0) → omit(%W/%D/%L). - Zero-sum WDL divide-by-zero guard — pathological
wdl 0 0 0engine output no longer producesNaN/Infinity. - Mate handling — uses
#+N/#-Nscore + 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-movesis stacked below.review-left(the board column, 256–320px tall), sooffsetTopincluded the board's full height —_targetwas way too large, clamped toscrollHeight - 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 previous game occasionally rendered on the initial-position board when entering review.
Root causes:
_computeInitialPositionAnnotationsreadgameState(the live mid-game state) instead ofreviewStates[0].state(the actual initial position shown at step 0).- The
'_initial'cache key was never cleared by_invalidateCachesForUndoneMoves(which only deletes numeric keys). It was cleared by_resetGameState(new game / import / setup-complete / FEN import), but if the user re-entered review without one of those entry points, the stale cache persisted.
Fix:
_computeInitialPositionAnnotationsnow readsreviewStates[0].statewith a fallback chain:reviewStates[0].state → reviewBaseState → gameState(defensive).enterReview()explicitly deletes the'_initial'key from_visualAnnotationsCacheat entry, forcing fresh computation each review session. Numeric keys (0..N-1) are deliberately preserved.
Phase 56 — Landscape Review Scroll + PGN Timeout + Timing Sync
🐛 Landscape review nav-button scroll-to-top
Symptom: In landscape review mode, clicking the navigation buttons (◀ ▶ ⏮ ⏭) caused the entire page to scroll back to the top.
Root cause: scrollIntoView({block:'center'}) scrolls all scrollable ancestors. In the nested .review-body > .review-moves layout, this dragged the outer container back to the top.
Fix: Replaced scrollIntoView with manual scrollTop computation on the inner .review-moves container only — preserves outer .review-body scroll position.
✨ PGN timeout annotations
Timeout games now emit proper PGN termination metadata, parallel to the existing resign logic:
[Termination "Time forfeit"]
1. e4 e5 2. Nf3 Nc6 ... {Black wins by timeout} 0-1
🐛 First-move timing synchronization
_turnStartTime and gameClocks are now reset in _resetGameUIState() (called by all game-start entry points). Previously _turnStartTime was only set once at module load — the first move's [%emt]/{Xs} annotation could be wildly inaccurate if the user spent time configuring the new game.
🐛 UCI command ordering refinement
setGameDifficulty()'s setoption UCI_LimitStrength/UCI_Elo/Skill Level commands are now sent before position fen in both engineGoTimed and engineGoInternal. Ensures the engine applies the difficulty settings to the search from the first move.
Phase 55 — Chess960 Castling Rook-Loss Fix
🐛 Critical: Rook disappears after O-O/O-O-O in certain Chess960 starting positions
Symptom: In Chess960 starting positions where the participating rook's source square IS the king's castling destination (e.g. King on d1, queenside rook on c1: O-O-O puts the King on c1, which is the rook's source), the rook would silently disappear from the statistics page board after castling.
Root cause: The _destEmpty check incorrectly rejected the Chess960 case where the king's destination square holds a same-color rook participating in the castling.
Fix: Replaced _destEmpty with _destValid — allows the destination square to be empty OR hold a same-color rook participating in the castling. Applied to both stats.html executeMove/buildSAN and game-logic.js _castleSide.
// Before:
const _destEmpty = !_destPiece;
if (_destEmpty && (!_is960 || _cr[color + 'Kingside'])) return 'kingside';
// After:
const _destValid = !_destPiece ||
(_destPiece.type === 'rook' && _destPiece.color === mv.piece.color);
if (_destValid && (!_is960 || _cr[color + 'Kingside'])) return 'kingside';Phase 54 — Custom Slider + 7 Code-Audit Fixes
✨ Pixel-perfect review progress bar / eval chart alignment
Replaced the native <input type="range"> with a custom slider (div elements for track/fill/thumb). The custom slider shares the same CSS container as the eval chart, and the thumb center is positioned via calc(ratio * 100%) — achieving true pixel-level alignment between the progress bar and the chart's first/last data points.
⚠️ Do NOT regress to native<input type="range">— WebKit's slider position is unreliable across Android WebView versions.
Other Phase 54 fixes
- Move-list scroll-into-view — only scroll when the active move is not visible, using
block:'nearest'. executeMoveasync callback — wrapped in try-catch to prevent async exceptions from corrupting the render state.ChessAudioEnginepartial-init reset —setEnabled/setVolumenow null-guard against partial initialization.- Engine heartbeat all-callbacks fix — heartbeat timestamp now updated in all engine callbacks (
onEngineProgress,onBestMove,onHintMove,onPonderProgress), not justonEngineEval. - MultiPV secondary-variation divergence — distinguish alternative vs. continuation variations correctly.
- PGN cascade-skip threshold — raised from 5 to
max(15, mainTokens.length * 0.1)to avoid skipping short legitimate games. - Render retry-loop guard —
_animRetryCountcapped at 10 to prevent infinite retry loops ifsetTimeoutnever fires (WebView suspended).
Phase 53 — Visual Annotation Overhaul + Version Bump
Version bumped to versionCode=110, versionName="1.1.0".
✨ Green arrow redefined: "escape path" → "check-response path"
The green arrow visual annotation now includes two categories:
- King escape moves — all legal king moves that escape check (correctly handling pins and discovered-check escapes). If the king has no legal escape, no green arrow is drawn from the king's starting square (fixes a v1.0.9 bug).
- Legal captures of the checking piece — all legal moves by the defending side that capture the checker (including legal en passant).
Chess960 compatibility:
isChess960CastlingLegalalready checks "king not in check", solegalMovesnever generates castling moves while in check — green arrows never incorrectly include castling.
✨ Red check arrow uses actual checker position
The red check arrow now points from the actual checking piece's position (correctly handling discovered checks and double checks), not from an inferred "last move origin".
Other Phase 53 changes
- Stats visual-annotation cutoff — the stats page's visual-annotation counts now respect the selected-move cutoff (was showing counts for the entire game regardless of selection).
- King-control-arrow legality filter — king-move arrows in the control heatmap / "square control info" panel / visual annotations are now filtered for legality (no arrows into pinned squares, no arrows that would leave the king in check).
- Nav-button center-align — review navigation button text is now center-aligned.
- King-position staleness fix —
_resetGameUIState()now clears all stale UI state, including cached king positions from a previous game. - FEN-import state pollution fix — importing a FEN no longer leaves residual state from the previous game.
exitSetupstate pollution fix — exiting setup mode no longer carries setup-only state into the game.- Portrait/landscape review layout unification — both orientations now use the
.review-top(board + moves) +.review-bottom(full-width controls) structure.
📦 Download
| Artifact | Size | Description |
|---|---|---|
Regalia-v1.1.0-release.apk |
~77 MB | Signed release APK (v1+v2+v3 signing schemes) |
Regalia-v1.1.0-manual-zh.html |
~818 KB | Chinese user manual (self-contained) |
Regalia-v1.1.0-manual-en.html |
~924 KB | English user manual (self-contained) |
UBIQUITOUS_LANGUAGE.md |
~32 KB | Pure-English domain terminology glossary (80+ terms) |
APK signing
✅ 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
Compatible with Xiaomi HyperOS 3 (Android 15) and all Android 5.0+ devices.
Engine binary (NOT in the source tarball)
The Stockfish 18 arm64-v8a-dotprod binary is excluded from the source tarball to keep it small and avoid redistributing the 114 MB engine binary. Download separately:
curl -sL -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.soSee BUILDING.md for full build instructions.
🔧 Build & Install
Requirements
- JDK 21 (Temurin 21.0.5+11 recommended; JRE-only is insufficient —
javacrequired) - Android SDK: API 35, Build-Tools 34.0.0, NDK 27.2.12479018, CMake 3.22.1
- Gradle 8.11.1 (wrapper included)
- Python 3 (for
build-chess.py— merges the 8 JS modules intochess.html)
Quick start
# 1. Place the engine binary (see above)
# 2. Configure local.properties
echo "sdk.dir=/path/to/android-sdk" > local.properties
# 3. Place a keystore at ../debug.keystore (storepass=android, alias=debug)
# or update signingConfigs.release in build.gradle
# 4. Build chess.html (merges 8 JS modules)
python3 build-chess.py
# 5. Build the release APK
./gradlew assembleRelease
# 6. Verify signatures
/path/to/android-sdk/build-tools/34.0.0/apksigner verify --verbose \
/tmp/regalia_build/Regalia/outputs/apk/release/Regalia-release.apkThe signed APK will be at /tmp/regalia_build/Regalia/outputs/apk/release/Regalia-release.apk (per buildDir in build.gradle).
Build troubleshooting
| Symptom | Fix |
|---|---|
CMake/ninja "manifest still dirty after 100 tries" |
find . -name "*.txt" -o -name "*.cpp" -o -name "*.cmake" | xargs touch (normalize future timestamps from unzip/tar) |
./gradlew: Permission denied |
chmod +x gradlew |
| Aliyun Maven mirror 502 | build.gradle/settings.gradle already place google()/mavenCentral() before the Aliyun mirror; if still 502, temporarily comment out the Aliyun blocks |
📁 Project Structure
Regalia/
├── src/main/
│ ├── assets/
│ │ ├── chess.src/ # 8 JS modules (merged by build-chess.py → chess.html)
│ │ │ ├── game-logic.js # Chess rules, move generation, i18n, castling detection
│ │ │ ├── chess960.js # Chess960 SP-ID, Shredder-FEN, 960 castling rules
│ │ │ ├── pgn-standard.js # PGN encoder/decoder, NAG, [%csl]/[%cal], formatEvalAnnotation
│ │ │ ├── worker-pool.js # Web Worker pool (Blob-URL, CSP-safe)
│ │ │ ├── ai-bridge.js # Engine communication, eval display, PGN export
│ │ │ ├── tablebase.js # Lichess Syzygy tablebase + PGN import
│ │ │ ├── eco-data.js # ECO opening classification data
│ │ │ ├── ui.js # Rendering, dialogs, review mode, ChessAudioEngine
│ │ │ └── index.html.tpl # CSS template (theme variables, responsive layout)
│ │ ├── chess.html # Built output (combined JS+CSS+HTML)
│ │ └── stats.html # Statistics page (fullscreen WebView)
│ ├── java/com/Regalia/
│ │ ├── MainActivity.java # WebView host, immersive mode, lifecycle
│ │ ├── StockfishNative.java # Engine process management, UCI protocol, 60+ JS interfaces
│ │ ├── StatsActivity.java # Fullscreen WebView for statistics
│ │ ├── ChessWebViewClient.java # Page load handler, render-process crash recovery
│ │ ├── EngineService.java # Foreground service for engine stability
│ │ ├── ChessApp.java # Application class, crash protection
│ │ ├── StabilizationHelper.java # Sensor-fusion board anti-shake (OIS principle)
│ │ ├── TlsSecurityHelper.java # TLS 1.2+ enforcement for tablebase API
│ │ └── RootDetector.java # Informational root detection (About dialog)
│ ├── cpp/
│ │ ├── engine_jni.cpp # JNI native chmod/renice (from DroidFish)
│ │ └── CMakeLists.txt
│ └── res/ # Android resources (strings, XML configs, launcher icons)
├── Manual/ # HTML user manuals (Chinese + English)
├── UBIQUITOUS_LANGUAGE.md # Domain terminology glossary (80+ terms, English)
├── NOTICE # Third-party component notices + version history
├── LICENSE-AGPL v3 / LICENSE-GPL v3 / LICENSE-Apache v2.0
├── PRIVACY.md / BUILDING.md / README.md
├── build.gradle / settings.gradle / gradle.properties
└── build-chess.py # Python build script (merges JS modules → chess.html)
📜 Licensing
Regalia is a combined work under dual licensing:
| Component | License | Files |
|---|---|---|
| Application (original) | AGPL v3 | MainActivity.java, ChessWebViewClient.java, EngineService.java, ChessApp.java, StabilizationHelper.java, RootDetector.java, TlsSecurityHelper.java, chess960.js, eco-data.js, build-chess.py, AndroidManifest.xml, strings.xml, build configs |
| Engine + DroidFish-derived | GPL v3 | StockfishNative.java, engine_jni.cpp, game-logic.js, ai-bridge.js, ui.js, tablebase.js, stats.html, index.html.tpl, pgn-standard.js, worker-pool.js, libstockfish.so |
| Gradle | Apache v2.0 | gradle/wrapper/* |
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.
7 README.license files provide per-directory license classification. See NOTICE for the full version history and third-party component attribution.
⚖️ Privacy
Regalia is a fully offline chess application. No personal data is collected, transmitted, or stored.
- No analytics, advertising SDKs, or tracking
- No user registration or accounts
- Only network-dependent feature: Syzygy endgame tablebase queries to the public Lichess API (
tablebase.lichess.ovh) — sends only FEN strings, no personal data. Forced over TLS 1.2+ viaTlsSecurityHelper.java. - All game data stored locally in app-private storage (
/data/data/com.Regalia/) - Engine binary integrity: ELF magic check + SHA-256 hash verification on first launch
See PRIVACY.md for the full policy.
🧪 Verification
Automated tests
| Test suite | Tests | Status |
|---|---|---|
test-phase57-plus.js |
18 | ✅ All pass |
test-phase58-pgn-annotation.js |
22 | ✅ All pass |
Test coverage includes:
- PGN tag-stripping regex (single-line + multi-line)
isChess960CastlingLegalking-position lookup (cached / no-cache / stale-cache / blocked-path / no-rights)formatEvalAnnotation(centipawn zh+en, positive/negative eval, mate White+Black, missing WDL, zero-sum WDL, missing depth, falsy cached, language toggle)- Built
chess.htmlpresence of all fixes
APK verification
✅ apksigner verify: v1 + v2 + v3 schemes all true
✅ aapt2 dump badging: versionCode=110, versionName=1.1.0
✅ AndroidManifest: no android:debuggable="true" (release build)
✅ chess.html: formatEvalAnnotation present, _moveNum%5===0 hook present, pgn_white_advantage i18n key present
✅ classes.dex: "race resolved" string present (stopLatch fix), "_writerLock" present (heartbeat fix)
🚀 What's Next
Items intentionally postponed to future versions (with rationale documented in NOTICE):
| Item | Reason |
|---|---|
ui.js split into 6 submodules (7,497 lines) |
Requires 6+ weeks of architectural work + dedicated test coverage. Not appropriate for a same-version hardening phase. |
StockfishNative.java split into focused helper classes (5,212 lines) |
Same as above — 60+ @JavascriptInterface methods must remain on the main class. |
| PGN parser unification (4 duplicate parsers) | stats.html is standalone (can't import from chess.src/); worker-pool.js runs in a Web Worker (separate scope). Unification requires careful extraction of a shared tokenizer module. |
Additional P0 concurrency hardening (_pendingBestMoveInfo 2s timer race, cleanupEngineResources sync granularity) |
Theoretical races that have not manifested in production across v1.0.8–v1.1.0. Require dedicated stress-testing. |
💬 Feedback & Contributions
- Bug reports & feature requests: Open an issue on GitHub
- Pull requests welcome — see
README.mdfor the contribution guide - Domain terminology questions: Refer to
UBIQUITOUS_LANGUAGE.md(80+ terms with definitions, ambiguity notes, and example conversations)
🙏 Acknowledgments
- Stockfish — the world-class open-source chess engine (GPL v3)
- DroidFish — the Android chess app from which Regalia derives its engine management, PGN handling, and core chess logic (GPL v3)
- Lichess — for the public Syzygy tablebase API
- The chess community for decades of specification work (PGN, FEN, SAN, UCI, Syzygy)
📖 Full changelog (Phase 53 → 58)
Phase 58 (2026.7.5) — Feature + P0 concurrency hardening
- ✨ Every-5-moves PGN
{}eval annotation (bilingual, White-perspective) - 🔒
stopLatchTOCTOU race fix (StockfishNative.java) - 🔒 Heartbeat deadlock fix — dedicated
_writerLock(StockfishNative.java)
Phase 57+ (2026.7.5) — Code-review-driven preventive hardening
- 🐛
pgn-standard.jsparseStandardPGNsingle-line PGN tag-stripping regex - ⚡
chess960.jsisChess960CastlingLegalking-position lookup optimization - 🐛
ai-bridge.jsWDL percentage display divide-by-zero guard - 🐛
StockfishNative.javapostJsCallbackactivity-lifecycle guard - 🐛
EngineService.javawake-lock bounded 30-minute timeout - 📝
res/README.licenseLIC-2 stale version reference - 📝 Added
UBIQUITOUS_LANGUAGE.md(pure-English, 80+ terms)
Phase 57 (2026.7.4) — Portrait review scroll + visual-annotation cache
- 🐛 Portrait review move-list scroll positioning (
offsetTop→getBoundingClientRect) - 🐛 Visual-annotation cache residue at review entry (
'_initial'key cleared inenterReview)
Phase 56 (2026.7.4) — Landscape review scroll + PGN timeout + timing sync
- 🐛 Landscape review nav-button scroll-to-top (
scrollIntoView→ manualscrollTop) - ✨ PGN timeout annotations (
[Termination "Time forfeit"]+{<color> wins by timeout}) - 🐛 First-move timing sync (
_turnStartTime+gameClocksreset in_resetGameUIState) - 🐛 UCI command ordering refinement (
setGameDifficultybeforeposition fen)
Phase 55 (2026.7.4) — Chess960 castling rook-loss fix
- 🐛
_destEmpty→_destValid(allows king destination to hold participating rook)
Phase 54 (2026.7.4) — Custom slider + 7 code-audit fixes
- ✨ Custom slider for pixel-perfect review progress bar / eval chart alignment
- 🐛 Move-list scroll-into-view (only when not visible,
block:'nearest') - 🐛
executeMoveasync callback try-catch - 🐛
ChessAudioEnginepartial-init reset - 🐛 Engine heartbeat all-callbacks fix
- 🐛 MultiPV secondary-variation divergence fix
- 🐛 PGN cascade-skip threshold (5 →
max(15, len*0.1)) - 🐛 Render retry-loop guard (
_animRetryCountmax 10)
Phase 53 (2026.7.3) — Visual annotation overhaul + version bump
- 🏷️ Version bumped to
versionCode=110, versionName="1.1.0" - ✨ Green arrow redefined: "escape path" → "check-response path" (king escape + capture checker)
- ✨ Red check arrow uses actual checker position (discovered check support)
- 🐛 Stats visual-annotation cutoff fix
- ✨ King-control-arrow legality filter
- ✨ Nav-button center-align
- 🐛 King-position staleness fix
- 🐛 FEN-import state pollution fix
- 🐛
exitSetupstate pollution fix - ✨ Portrait/landscape review layout unification
AI-GEN: AI assisted — this release note was AI-assisted and has been reviewed for AGPL v3 compliance.
Copyright (C) 2026 Regalia. Licensed under GNU Affero General Public License v3 (AGPL v3).