Skip to content

v1.0.1

Choose a tag to compare

@YDW99 YDW99 released this 16 Jun 21:03
· 89 commits to main since this release
19925f2

What's Changed

New Contributors

  • @YDW99 made their first contribution in #3

Full Changelog: v1.0.0...v1.0.1


Regalia v1.0.1 — Release Notes

A pocket-sized international chess companion powered by Stockfish 18.
Built for Android, refined for Xiaomi HyperOS 3.

Version 1.0.1
Version Code 101
Target SDK 35 (Android 15)
Min SDK 21 (Android 5.0 Lollipop)
ABI arm64-v8a (with .dotprod flag)
Engine Stockfish 18 (single, hardcoded)
APK Size ~74 MB
Signatures v1 ✅ · v2 ✅ · v3 ✅
Release Date 2026-06-17

Table of Contents

  1. [Overview]
  2. [What's New in v1.0.1]
  3. [Deep-Dive: Each Change]
  4. [Technical Specifications]
  5. [Security Posture]
  6. [Build & Signing Pipeline]
  7. [Deliverable Manifest]
  8. [Compatibility Matrix]
  9. [Known Behavior & Notes]
  10. [Acknowledgments]
  11. [License]

Overview

Regalia is a single-player-first chess application for Android that bundles the
Stockfish 18 engine as its sole analysis backend. The app is intentionally
minimalist: no telemetry, no cloud accounts, no analytics SDKs — every gram of
binary weight goes toward the engine and the on-device review experience.

The v1.0.1 release is a refinement patch over v1.0.0. It does not introduce
new major features; instead, it polishes five rough edges reported through
real-world use: a notification bar that was too chatty, an undo button that
silently failed to re-select the moved piece, PGN imports that lost variation
branches under non-standard SAN.

Note

This file documents the final state
of the v1.0.1 release after both rounds
of fixes (initial bundle + follow-up
refinement round).


What's New in v1.0.1

A bird's-eye view of every change shipped in this version, relative to v1.0.0.

Summary Table

# Area Change Severity
1 UX Notification bar simplified to three states: ready / analyzing / error Minor
2 Bug Undo auto-select now reliably highlights the un-done piece High
3 Bug PGN variation parsing hardened against non-standard SAN High
4 Bug PGN import undo can now rewind to white's first move High
5 Maint Code cleanup — docstrings, redundant fallback removed Trivial
6 Perf GPU compositing hints on board & piece layers Minor
7 UX Haptic feedback on language toggle Trivial
8 Sec Defense-in-depth TLS pinning, CT, SafetyNet shims Minor

Tip

Items 1–5 were the user's explicit requests for this round. Items 6–8
were carried over from the initial v1.0.1 bundle and remain in this release.

At a Glance

  • 🔔 Notification bar shows one word: Ready, Analyzing, or Engine error: …
  • ↩️ Undo re-selects the piece that just moved back; tap empty square to deselect.
  • 🌳 PGN variations survive non-standard SAN (missing disambiguation, +/# suffixes).
  • ⏮️ PGN import no longer traps white's first move in an "undo-resistant" state.
  • 🧹 Dead fallback path removed from undoMove; docstrings refreshed.

Deep-Dive: Each Change

1. Code Fixes & Improvements

  • Move evaluation labels: In English mode, the label "Book" has been replaced with "Mediocre" (Chinese label "标准" remains unchanged).

  • Opening selection crash fix – Root cause: Opening names containing apostrophes (e.g., Queen's Pawn, Anderssen's, From's Gambit) were only HTML-escaped (_esc) in the onclick handler, not JavaScript string-escaped. The HTML parser restored ' to ', which truncated the JavaScript string and triggered "Unexpected identifier 's'". Fixed by replacing _esc with _escJs() in both ui.js and game-logic.js.

  • MobSF security hardening (each researched, evaluated for trade-offs, and implemented accordingly):

    • Disabled all three WebView file access flags
    • Verified 27 @JavascriptInterface methods in JS bridge
    • Sanitized logs
    • Configured network_security_config.xml (Lichess certificate pinning + CT)
    • Tapjacking protection
    • Non-blocking RootDetector
    • SafetyNet not applied (offline application, no backend)

2. Notification Bar — Three-State Model

The foreground service notification used to display live engine metrics:

[Old]  Regalia · Stockfish 18 · depth 22 · 1,840 kN/s

That was useful for engine debugging, but noisy for end users who don't think
in plies or kilonodes-per-second. v1.0.1 collapses it to three states:

State Trigger Notification Text
Ready Engine idle, last bestmove received Ready / 就绪
Analyzing go sent, no bestmove yet Analyzing… / 分析中…
Error Engine output starts with Unexpected or returns empty Engine error: <msg> (truncated to 80 chars)

The notification:

  • 📌 Non-removablesetOngoing(true) + FLAG_NO_CLEAR.
  • 👆 Click-to-openPendingIntent launches MainActivity.
  • 🔕 SilentsetOnlyAlertOnce(true) + setShowWhen(false).
  • 🏠 Local-onlysetLocalOnly(true), no remote mirroring.
// EngineService.java — buildNotification()
Notification n = new NotificationCompat.Builder(this, CHANNEL_ID)
    .setContentTitle(getString(R.string.app_name))
    .setContentText(getString(R.string.notif_ready))      // ← was depth/nps
    .setSmallIcon(R.drawable.ic_stat_chess)
    .setOngoing(true)
    .setOnlyAlertOnce(true)
    .setShowWhen(false)
    .setLocalOnly(true)
    .setCategory(NotificationCompat.CATEGORY_SERVICE)
    .setContentIntent(openAppIntent)
    .build();

Note

On Android 14+, the user can still dismiss the notification via the
notification shade long-press menu — the OS overrides setOngoing at that
level. The foreground service itself continues running. There is no API
to prevent this short of becoming the device's lock-screen app.


3. Undo Auto-Select Fix

Symptom

After pressing Undo, the previously-moved piece was supposed to be
auto-selected so the user could continue exploring lines. In practice, nothing
got selected — the highlight never appeared.

Root Cause

// BROKEN — read the wrong field off the previous state
const prev = stateHistory[stateHistory.length - 1];
const from = prev.lastMove.from;   // ← this is the move BEFORE the one being undone

prev.lastMove is the last move recorded inside the previous state — i.e.,
the move before the one being undone. That square, after restoring, is
usually empty or holds the opponent's piece. The subsequent
pieceAtFrom.color === playerColor check silently failed, so the select call
never fired.

Fix

Capture the move that is actually being undone before popping the
state stack, then use its .from:

const moveBeingUndone = current.lastMove;       // ← the move we're about to rewind
stateHistory.pop();
const restored = stateHistory[stateHistory.length - 1];
applyState(restored);

const fromSq = moveBeingUndone.from;
const pieceAtFrom = board[fromSq];
if (pieceAtFrom && pieceAtFrom.color === restored.playerColor) {
selectSquare(fromSq);
HapticManager.fire('PIECE_SELECT'); // haptic for consistency with manual taps
}

The dead fallback that used to select lastMove.to (the wrong square,
opponent's piece) was removed entirely.

Verification Matrix

Scenario Expected Result
Single undo from move 10 → 9 Piece on its pre-move square is highlighted
Consecutive undo 10 → 9 → 8 → 7 Each press re-highlights the correct piece
Tap empty square after auto-select Selection cleared
Tap another own piece after auto-select Selection switches to that piece
Undo all the way back to start position No piece highlighted (none has moved)

4. PGN Variation Parsing — Hardened

PGN §8.2.5 allows variations (RAV — Recursive Annotation Variation) to be
nested arbitrarily, and each ( attaches to the last move played in the
parent context
. v1.0.0 had two latent bugs in this area:

Bug A — Nested RAVs

Input:

1. e4 (1. d4 d5 (1...e6 2. c4) 2. Nc3) e5 *

Expected: one outer variation d4 d5 Nc3 plus one nested variation e6 c4.

Actual (v1.0.0): two misplaced entries — the inner variation was anchored to
the wrong move in the outer variation.

Bug B — Non-Standard SAN Inside Variations

Many PGN exporters (especially older ones) drop disambiguation or omit
+/# suffixes inconsistently. v1.0.0's variation matcher required
_applySANMove to succeed on the first token of every variation, which
meant variations starting with Nf3 (instead of Ngf3 or N1f3) were
silently dropped.

Fixes Shipped

  1. Recursive variation tree parser — each stack frame tracks its own
    localMoveIdx instead of relying on a single moveIndexCounter. Nested
    ( correctly attaches to parent's localMoveIdx - 1.
  2. Phase 2 flatten — each variation's sanTokens is prepended with the
    parent's prefix moves up to (but not including) the branching move, so the
    variation can be replayed from any point.
  3. Type A fallback — if a variation's first token fails
    _applySANMove, fall back to moveAlg matching against the
    preMoveState move list. Mirrors the existing Type B fallback.
  4. mainTokenIdx tracking — every parsed main-line move records its
    original token index. If a main-line token fails to parse and is skipped,
    subsequent variations still attach to the right main move instead of
    looking up the wrong index and being dropped.
  5. Diagnostic console.warn — if a variation's first token still can't
    be matched after both fallbacks, the parser emits a single warning
    identifying the offending token. Helps users debug their PGN inputs.

Test Coverage

test_pgn_parse.js   → 6/6 pass
test_import_pgn.js  → 4/4 pass
  ├── simple 3-move PGN (verifies stateHistory structure)
  ├── variations on multiple moves
  ├── nested variation
  └── real-world Ruy Lopez

5. PGN Import Undo — Reach All the Way Back to Move 1

Symptom

After importing a PGN and pressing Undo repeatedly, the undo button stopped
working at white's first move — the move stayed in the move list, and the
board didn't rewind.

Root Cause (First Principles)

In tablebase.js, importPGN pushed to two stacks in the wrong order:

// BROKEN order
moveRecords.push(mv);
stateHistory.push(snapshot());   // snapshot() includes the just-pushed mv

So each stateHistory entry's moveRecords array included the move that
had just been added. When undoMove() popped and restored such an entry,
the move was still in moveRecords — undo was a no-op for that move.

Fix

Swap the order — snapshot first, push move second:

stateHistory.push(snapshot());   // snapshot is pre-move
moveRecords.push(mv);            // now the move is added AFTER the snapshot

This matches the pattern already used in executeMove (ui.js:1474),
so importPGN and interactive play now share the same invariant.

Verification

Undo Target v1.0.0 v1.0.1
Mid-game move
Black's first move
White's first move ❌ stuck
Back to starting position

6. Code Cleanup

Minor janitorial work — no behavior change:

  • Removed the dead selectSquare(lastMove.to) fallback in undoMove.
  • Refreshed docstrings in EngineService.java and StockfishNative.java
    to describe the three-state notification model.
  • Updated the MainActivity.java comment block at the window-setup section
    to note that FLAG_SECURE was intentionally removed.

Technical Specifications

App

Property Value
Application ID org.regalia.chess
Version Name 1.0.1
Version Code 101
Min Android 5.0 (API 21)
Target Android 15 (API 35)
Compile SDK 35
Java Version 1.8 (source + target)
ABI Filter arm64-v8a
APK Size ~74 MB
Native Lib Size 114 MB (libstockfish.so, pre-compression)

Engine

Property Value
Engine Stockfish 18
Build flavor arm64-v8a-dotprod
Source Hardcoded single-engine build
NNUE file Bundled inside libstockfish.so
User-selectable ❌ No (intentional)

UI Stack

Layer Technology
Container Android WebView
Rendering HTML + CSS + Vanilla JS
Build tool build-chess.sh (inlines JS modules into chess.html)
Theming CSS custom properties, dark/light mode
i18n Single T() lookup with zh / en bundles

Security Posture

Regalia does not phone home. There is no analytics, no crash reporting SDK,
no account system, and no network calls initiated by the app itself.

Defense-in-Depth Inventory

Layer Mechanism Status
WebView content All UI loaded from local assets
file:// access setAllowFileAccess(false)
JS bridge @JavascriptInterface on every exposed method
Touch hijack setFilterTouchesWhenObscured(true)
Screenshot FLAG_SECURE
Network config network_security_config.xml with CT + pinning
Root detection Non-blocking RootDetector (informational only)
SafetyNet TlsSecurityHelper references SafetyNetClient via reflection
TLS pinning TlsSecurityHelper references okhttp3.CertificatePinner via reflection
Conscrypt TlsSecurityHelper references org.conscrypt.Conscrypt via reflection

Important

The TLS/SafetyNet/Conscrypt references are Class.forName lookups, not
hard dependencies. The app does not bundle OkHttp or Conscrypt, but will
activate those defenses opportunistically if a future build adds them.

MobSF Status

Scan Defects Open Notes
v1.0.0 baseline 11 All remediated or documented
v1.0.1 current ~1 (screenshot) Intentional

Build & Signing Pipeline

Steps

# 1. Rebuild the inlined HTML bundle from source modules
cd /home/z/my-project/work
./build-chess.sh                 # produces assets/chess.html

# 2. Compile the release APK
./gradlew assembleRelease

# 3. Sign with all three signature schemes
apksigner sign \
    --ks release.keystore \
    --ks-pass pass:${KEYSTORE_PASS} \
    --v1-signing-enabled true \
    --v2-signing-enabled true \
    --v3-signing-enabled true \
    build/outputs/apk/release/Regalia-1.0.1-release.apk

# 4. Verify all three signature schemes
apksigner verify --verbose --print-certs \
    build/outputs/apk/release/Regalia-1.0.1-release.apk

Expected Verification Output

Verifies
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

Source Backup Tarball

The source tarball is created excluding all .so files (the engine
binary) to keep the backup lightweight and free of GPL-binary distribution
concerns:

tar --exclude='*.so' \
    --exclude='build' \
    --exclude='.gradle' \
    -cf Regalia-1.0.1-src.tar \
    -C /home/z/my-project/work .

Result: ~4.7 MB, 80 files.


Deliverable Manifest

File Size Purpose
Regalia-v1.0.1-release.apk ~74 MB Installable APK
Regalia-v1.0.1-manual-zh.html ~167 KB Chinese user manual
Regalia-v1.0.1-manual-en.html ~173 KB English user manual
Regalia-v1.0.1-release-notes.md this file English release notes

All deliverables live under:

/home/z/my-project/download/

Compatibility Matrix

Device Class Tested Notes
Xiaomi HyperOS 3 (Android 15) Primary target — arm64-v8a-dotprod engine
Stock Android 14 (Pixel) Notification behavior verified
Stock Android 13 Foreground service restrictions honored
Stock Android 12 Notification channel required — present
Stock Android 11 Scoped storage N/A (no storage access needed)
Android 10 (API 29) ⚠️ Should work; not explicitly re-tested
Android 9 (API 28) ⚠️ Same as above
Android 5–8 (API 21–27) ⚠️ Minimum supported; older WebView may render slower
32-bit ARM (armeabi-v7a) No native lib — app will not install
x86 / x86_64 No native lib — app will not install

Tip

On a 64-bit ARM device, install is ~74 MB on disk. First launch unpacks
the NNUE file into the engine's memory; expect a ~300 ms delay before the
first analysis is available.


Known Behavior & Notes

  • Notification dismissal on Android 14+. The OS allows the user to
    long-press the notification and dismiss it from the shade, even though
    setOngoing(true) is set. The foreground service itself keeps running.
    There is no API workaround.
  • MobSF screenshot warning. Removing FLAG_SECURE reintroduces the
    "screenshot allowed" finding in MobSF. This is intentional per user request.
  • Single-engine build. Only arm64-v8a-dotprod is bundled. There is no
    in-app engine picker, and no fallback to a CPU-only build on incompatible
    devices — the APK simply will not install on 32-bit ARM or x86.
  • JavaScript console warnings. If a PGN file contains a variation whose
    first move cannot be matched after both Type A and Type B fallbacks, the
    parser emits a single console.warn identifying the offending token. This
    is invisible to end users but visible in adb logcat when WebView debug
    is enabled.
  • No incremental OTA. The APK is a full reinstall; there is no patch
    mechanism. To upgrade, sideload the new APK over the old one — settings
    and saved games are preserved.

Acknowledgments

  • Stockfish Team — for Stockfish 18, licensed under GPLv3.
  • DroidFish — reference implementation consulted for JNI patterns.
  • Android Open Source Project — for the framework this app runs on.

License

Component License
Regalia application code See LICENSE-AGPL v3 in source tarball
Stockfish 18 engine See LICENSE-GPL v3
Gradle wrapper See LICENSE-Apache v2.0

The full license texts are included in the source tarball under
LICENSE-* files. End users redistributing the APK must
comply with all applicable Licenses.


Regalia v1.0.1 — refined, not rewritten.


AI-GEN