Regalia v1.1.0 #29
YDW99
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
What's Changed
Full Changelog: v1.0.9...v1.1.0
Regalia v1.1.0
🎯 Release Highlights
This release ships 8 development phases (Phase 53 → 58) layered on top of the v1.0.9 baseline, delivering:
stopLatchTOCTOU race, heartbeat deadlock,postJsCallbacklifecycle guard{}eval annotation (bilingual, White-perspective)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 annotationAt 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 viaT()reading the global_langvariable, so it switches between Chinese and English based on the app's current language mode.Format:
Examples:
均势 (-0.10) D22 SD34 (1%W/96%D/3%L)Equal (-0.10) D22 SD34 (1%W/96%D/3%L)白方将杀 (#+3) D15 SD20 (100%W/0%D/0%L)White mates (#+3) D15 SD20 (100%W/0%D/0%L)Design choices:
[%eval]values are always from White's POV.D## SD##; no WDL (all-1or sum ≤ 0) → omit(%W/%D/%L).wdl 0 0 0engine output no longer producesNaN/Infinity.#+N/#-Nscore + a localized "White mates" / "Black mates" label.{}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:
🔒 P0 Concurrency Fix 1 —
stopLatchTOCTOU raceSymptom: In rare timing windows, the
bestmovefrom a stopped ponder search could incorrectly arm the_discardingPonderBestmoveflag, causing the next legitimatebestmoveto be silently dropped — corrupting the engine state machine.Root cause: The
bestmovehandler inreadEngineOutput()read_stopLatch(avolatilefield) without holding_stopLatchLock. This created a classic time-of-check-to-time-of-use race withstopAndWaitForBestmove's timeout path:Fix: The
bestmovehandler now atomically captures-and-clears_stopLatchunder_stopLatchLock:The timeout path only arms the discard flag if it still owns the latch (
_stopLatch == stopLatchunder the lock). Exactly one consumer "owns" the latch — no race.🔒 P0 Concurrency Fix 2 — Heartbeat deadlock
Symptom:
shutdown()could block for the full 1-secondjointimeout 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) wassynchronizedonStockfishNative.this— the same monitor used bystartHeartbeat()(which issynchronized). Ifshutdown()ran while the heartbeat held thethismonitor inside the writer I/O call,shutdown's_heartbeatThread.join(1000)would wait for the heartbeat to releasethis— but the heartbeat was blocked on I/O.Fix: Introduced a dedicated
_writerLock(private final Object) forengineWriteraccess in the heartbeat path._writerLockis decoupled from thethismonitor, soshutdown's interrupt/join is not blocked by heartbeat's writer access.cleanupEngineResources()andrecoverEngine()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:
pgn-standard.jsparseStandardPGNsingle-line PGN tag-stripping regex —/^\[[\s\S]*?\]/gmonly matched the first tag when all tags + movetext were on ONE line (the^anchor withgmflags 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.chess960.jsisChess960CastlingLegalking-position lookup — was scanning the entire back rank (up to 8 board reads); now reads the cacheds.wk/s.bkfields directly (maintained bysyncHash()andcloneS()), with a defensive board-scan fallback.ai-bridge.jsif(total > 0)check before dividing_sfWdlW/_sfWdlD/_sfWdlLby their sum.StockfishNative.javapostJsCallbackactivity-lifecycle guard — skipsevaluateJavascriptwhen host ActivityisFinishing()/isDestroyed(). PreventsIllegalStateExceptioncrash on HyperOS 3 during engine-init retries after user exit.EngineService.javawakeLock.acquire(30L * 60L * 1000L). Prevents indefinite CPU wake if OEM silently kills the service andonDestroynever runs.res/README.licensestrings.xmldescription updated fromv1.0.8tov1.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
scrollIntoViewwith manualscrollTopcomputation) used_rAct.offsetTopto compute the active move's position. However,offsetTopreturns the distance from the element's outer border to the top of itsoffsetParent's inner border — and.rmv-block'soffsetParentis.review-overlay(position:fixed), not_rList(.review-moveshas nopositionset)..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
offsetTopwithgetBoundingClientRect()-based calculation relative to_rList: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).'_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-moveslayout, this dragged the outer container back to the top.Fix: Replaced
scrollIntoViewwith manualscrollTopcomputation on the inner.review-movescontainer only — preserves outer.review-bodyscroll position.✨ PGN timeout annotations
Timeout games now emit proper PGN termination metadata, parallel to the existing resign logic:
🐛 First-move timing synchronization
_turnStartTimeandgameClocksare now reset in_resetGameUIState()(called by all game-start entry points). Previously_turnStartTimewas 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()'ssetoption UCI_LimitStrength/UCI_Elo/Skill Levelcommands are now sent beforeposition fenin bothengineGoTimedandengineGoInternal. 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
_destEmptycheck incorrectly rejected the Chess960 case where the king's destination square holds a same-color rook participating in the castling.Fix: Replaced
_destEmptywith_destValid— allows the destination square to be empty OR hold a same-color rook participating in the castling. Applied to bothstats.htmlexecuteMove/buildSANandgame-logic.js_castleSide.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 viacalc(ratio * 100%)— achieving true pixel-level alignment between the progress bar and the chart's first/last data points.Other Phase 54 fixes
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.onEngineProgress,onBestMove,onHintMove,onPonderProgress), not justonEngineEval.max(15, mainTokens.length * 0.1)to avoid skipping short legitimate games._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:
✨ 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
_resetGameUIState()now clears all stale UI state, including cached king positions from a previous game.exitSetupstate pollution fix — exiting setup mode no longer carries setup-only state into the game..review-top(board + moves) +.review-bottom(full-width controls) structure.📦 Download
Regalia-v1.1.0-release.apkRegalia-v1.1.0-manual-zh.htmlRegalia-v1.1.0-manual-en.htmlUBIQUITOUS_LANGUAGE.mdAPK signing
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-dotprodbinary is excluded from the source tarball to keep it small and avoid redistributing the 114 MB engine binary. Download separately:See
BUILDING.mdfor full build instructions.🔧 Build & Install
Requirements
javacrequired)build-chess.py— merges the 8 JS modules intochess.html)Quick start
The signed APK will be at
/tmp/regalia_build/Regalia/outputs/apk/release/Regalia-release.apk(perbuildDirinbuild.gradle).Build troubleshooting
CMake/ninja "manifest still dirty after 100 tries"find . -name "*.txt" -o -name "*.cpp" -o -name "*.cmake" | xargs touch(normalize future timestamps fromunzip/tar)./gradlew: Permission deniedchmod +x gradlewbuild.gradle/settings.gradlealready placegoogle()/mavenCentral()before the Aliyun mirror; if still 502, temporarily comment out the Aliyun blocks📁 Project Structure
📜 Licensing
Regalia is a combined work under dual licensing:
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 configsStockfishNative.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.sogradle/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.licensefiles provide per-directory license classification. SeeNOTICEfor 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.
tablebase.lichess.ovh) — sends only FEN strings, no personal data. Forced over TLS 1.2+ viaTlsSecurityHelper.java./data/data/com.Regalia/)See
PRIVACY.mdfor the full policy.🧪 Verification
Automated tests
test-phase57-plus.jstest-phase58-pgn-annotation.jsTest coverage includes:
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)chess.htmlpresence of all fixesAPK verification
🚀 What's Next
Items intentionally postponed to future versions (with rationale documented in
NOTICE):ui.jssplit into 6 submodules (7,497 lines)StockfishNative.javasplit into focused helper classes (5,212 lines)@JavascriptInterfacemethods must remain on the main class.stats.htmlis standalone (can't import fromchess.src/);worker-pool.jsruns in a Web Worker (separate scope). Unification requires careful extraction of a shared tokenizer module._pendingBestMoveInfo2s timer race,cleanupEngineResourcessync granularity)💬 Feedback & Contributions
README.mdfor the contribution guideUBIQUITOUS_LANGUAGE.md(80+ terms with definitions, ambiguity notes, and example conversations)🙏 Acknowledgments
📖 Full changelog (Phase 53 → 58)
Phase 58 (2026.7.5) — Feature + P0 concurrency hardening
{}eval annotation (bilingual, White-perspective)stopLatchTOCTOU race fix (StockfishNative.java)_writerLock(StockfishNative.java)Phase 57+ (2026.7.5) — Code-review-driven preventive hardening
pgn-standard.jsparseStandardPGNsingle-line PGN tag-stripping regexchess960.jsisChess960CastlingLegalking-position lookup optimizationai-bridge.jsWDL percentage display divide-by-zero guardStockfishNative.javapostJsCallbackactivity-lifecycle guardEngineService.javawake-lock bounded 30-minute timeoutres/README.licenseLIC-2 stale version referenceUBIQUITOUS_LANGUAGE.md(pure-English, 80+ terms)Phase 57 (2026.7.4) — Portrait review scroll + visual-annotation cache
offsetTop→getBoundingClientRect)'_initial'key cleared inenterReview)Phase 56 (2026.7.4) — Landscape review scroll + PGN timeout + timing sync
scrollIntoView→ manualscrollTop)[Termination "Time forfeit"]+{<color> wins by timeout})_turnStartTime+gameClocksreset in_resetGameUIState)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
block:'nearest')executeMoveasync callback try-catchChessAudioEnginepartial-init resetmax(15, len*0.1))_animRetryCountmax 10)Phase 53 (2026.7.3) — Visual annotation overhaul + version bump
versionCode=110, versionName="1.1.0"exitSetupstate pollution fixAI-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).
This discussion was created from the release Regalia v1.1.0.
All reactions