Skip to content

Commit 826d17a

Browse files
authored
Merge pull request #159 from doublegate/fix/v1.8.1-demo-timer
fix(android): v1.8.1 — 8-min demo + confirm debug-unlock absent from release
2 parents 503c128 + 26c4195 commit 826d17a

9 files changed

Lines changed: 285 additions & 45 deletions

File tree

‎.gitignore‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,20 @@ flamegraph.svg
198198
Thumbs.db
199199
Desktop.ini
200200

201+
# --- Android (v1.8.0) ---
202+
# The android/ Gradle module has its own .gitignore (build/, .gradle/, jniLibs/,
203+
# keystore.properties, local.properties, the wrapper jar); these belt-and-suspenders
204+
# rules cover the same outputs if a tool scans from the repo root. The committed
205+
# Gradle wrapper (gradlew[.bat] + gradle-wrapper.jar) and keystore.properties.example
206+
# are intentionally tracked.
207+
android/**/build/
208+
android/.gradle/
209+
android/local.properties
210+
android/keystore.properties
211+
*.jks
212+
*.keystore
213+
android/app/src/main/jniLibs/
214+
201215
# --- Pre-commit ---
202216
.pre-commit-cache/
203217

‎CHANGELOG.md‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
1616
## [Unreleased]
1717

18+
## [1.8.1] - 2026-06-19 - "Android" (Patch)
19+
20+
### Changed
21+
22+
- **Free-tier demo session shortened from 10 minutes to 8 minutes** per launch
23+
(`DEMO_SESSION_SECONDS`, Android). Debug builds keep the 60-second testing
24+
value. No other behaviour changes; the paid Full Unlock is unaffected.
25+
26+
### Security
27+
28+
- **Confirmed the debug "Full Unlock" override is absent from the Play (release)
29+
build.** The `DBG:unlock`/`DBG:demo` toggle, the `LicenseManager.debugForceUnlocked`
30+
body, and the ROM autoload are all gated on `BuildConfig.DEBUG`, which is the
31+
compile-time constant `false` in the `release` variant — R8 strips the dead
32+
branches, so the override cannot be reached (or even referenced by string) in the
33+
shipped AAB. Verified by inspecting the release `classes*.dex` (no `DBG:unlock`
34+
literal) and running the release variant (no debug toggle in the UI).
35+
1836
## [1.8.0] - 2026-06-19 - "Android" (Platform Release)
1937

2038
> The first **platform** (not accuracy) release: a complete, shippable Android

‎Cargo.lock‎

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ members = [
1616
]
1717

1818
[workspace.package]
19-
version = "1.8.0"
19+
version = "1.8.1"
2020
edition = "2024"
2121
rust-version = "1.96"
2222
license = "MIT OR Apache-2.0"
@@ -27,17 +27,17 @@ keywords = ["nes", "emulator", "famicom", "6502", "retro"]
2727
categories = ["emulators", "games"]
2828

2929
[workspace.dependencies]
30-
rustynes-core = { path = "crates/rustynes-core", version = "1.8.0" }
31-
rustynes-cpu = { path = "crates/rustynes-cpu", version = "1.8.0" }
32-
rustynes-ppu = { path = "crates/rustynes-ppu", version = "1.8.0" }
33-
rustynes-apu = { path = "crates/rustynes-apu", version = "1.8.0", default-features = false }
34-
rustynes-mappers = { path = "crates/rustynes-mappers", version = "1.8.0" }
35-
rustynes-netplay = { path = "crates/rustynes-netplay", version = "1.8.0" }
36-
rustynes-script = { path = "crates/rustynes-script", version = "1.8.0" }
37-
rustynes-test-harness = { path = "crates/rustynes-test-harness", version = "1.8.0" }
30+
rustynes-core = { path = "crates/rustynes-core", version = "1.8.1" }
31+
rustynes-cpu = { path = "crates/rustynes-cpu", version = "1.8.1" }
32+
rustynes-ppu = { path = "crates/rustynes-ppu", version = "1.8.1" }
33+
rustynes-apu = { path = "crates/rustynes-apu", version = "1.8.1", default-features = false }
34+
rustynes-mappers = { path = "crates/rustynes-mappers", version = "1.8.1" }
35+
rustynes-netplay = { path = "crates/rustynes-netplay", version = "1.8.1" }
36+
rustynes-script = { path = "crates/rustynes-script", version = "1.8.1" }
37+
rustynes-test-harness = { path = "crates/rustynes-test-harness", version = "1.8.1" }
3838
# v1.8.0 "Android" — the shared mobile bridge, consumed by rustynes-android (and
3939
# v1.9.0's rustynes-ios). Path dep; the version req is a floor.
40-
rustynes-mobile = { path = "crates/rustynes-mobile", version = "1.8.0" }
40+
rustynes-mobile = { path = "crates/rustynes-mobile", version = "1.8.1" }
4141

4242
# v1.1.0 beta.3 (Workstream E) — Lua scripting. `vendored` compiles Lua 5.4
4343
# from source (no system Lua needed); native-only (the wasm/no_std builds never

‎README.md‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
1111
[![Build Status](https://github.com/doublegate/RustyNES/workflows/CI/badge.svg)](https://github.com/doublegate/RustyNES/actions)
1212
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](#license)
13-
[![Version](https://img.shields.io/badge/version-v1.7.1-blue.svg)](https://github.com/doublegate/RustyNES/releases)
13+
[![Version](https://img.shields.io/badge/version-v1.8.1-blue.svg)](https://github.com/doublegate/RustyNES/releases)
1414
[![Rust: 1.96](https://img.shields.io/badge/rust-1.96-orange.svg)](rust-toolchain.toml)
1515
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS%20%7C%20Web-lightgrey.svg)](#platform-support)
1616
[![AccuracyCoin](<https://img.shields.io/badge/AccuracyCoin-100%25%20(139%2F139)-brightgreen.svg>)](#compatibility-and-accuracy)
@@ -645,13 +645,16 @@ workspace API docs (rustdoc) at
645645

646646
## Version History
647647

648-
The current release is **v1.7.1**, a bugfix / polish patch on **v1.7.0 "Forge"** (the
649-
writable + programmable tooling, accuracy, mapper-breadth, and reach release on the
650-
cycle-accurate v1.0.0 production core, which follows the v1.6.0 "Studio" TAS-authoring line
651-
and the v1.5.0 "Lens" insight/scriptability line). The road so far:
648+
The current release is **v1.8.1 "Android"**, a patch on the first **platform** (not
649+
accuracy) release **v1.8.0 "Android"** — a complete, shippable Android app, verified
650+
end-to-end on a Samsung Galaxy Z Fold 7. It builds on the bugfix patch **v1.7.1** and
651+
the writable + programmable **v1.7.0 "Forge"** line, on the cycle-accurate v1.0.0
652+
production core. The road so far:
652653

653654
| Version | Highlights |
654655
| ---------- | ------------------------------------------------------------------------------------------- |
656+
| **v1.8.1** | "Android" patch — the free-tier demo is trimmed from 10 minutes to **8 minutes** per launch (the paid Full Unlock is unaffected), and the debug "Full Unlock" override is confirmed **R8-stripped from the release AAB** (0 occurrences of the debug-toggle symbols in the shipped `classes*.dex`). |
657+
| **v1.8.0** | "Android" — the first **platform** release: a complete Android app on the byte-identical core (so AccuracyCoin holds 100% / 139/139 unchanged). A shared **`rustynes-mobile`** UniFFI bridge + a **`rustynes-android`** platform crate + a Jetpack **Compose** shell; **audio** (`AudioTrack`), **touch + hardware-gamepad** input, **save-states / battery-SRAM / a recent-ROMs library** (SAF persistable URIs, save-on-background + auto-resume), **pause / fast-forward / mute**, a responsive **foldable-aware + immersive** UI (cover + unfolded inner screen), **GPU CRT / scanline** post-processing (AGSL `RuntimeShader`), and a thermal-throttle fast-forward backoff. Distributed on **Google Play** as a free download with a one-time **$2.99 "Full Unlock"** (Play Billing) — the free tier is a **10-minute demo** without save-states / resume / battery-SRAM, otherwise feature-complete. Verified **arm64 R8 release AAB**. The full wgpu-`SurfaceView` native renderer (reusing the desktop WGSL shader stack) + netplay / RetroAchievements / Lua on mobile are the documented next increment. |
655658
| **v1.7.1** | Patch on "Forge" — seven fixes: a **ROM-close crash** (skip the `write_texture` upload on a pixel-slice length mismatch so the release build no longer aborts wgpu; `close_rom` presents a clean blank frame); **clean pause/unpause** (a pacing-timer `break_phase()` reset on resume + a sticky audio pause gate → no spurious `produced_max_ms` spike, zero underruns); a **Documentation pane overhaul** (word-wrap at any UI scale, a collapsible multi-level sidebar tree with intra-doc links, `[Unreleased]` ordered last); an **exhaustive README rewrite**; **Tools-menu icon** fixes for NSF Player / Pixel Inspector (glyph swap dodging egui's Ubuntu-Light `fi`/`fl` PUA-ligature collision); **removal of the vestigial "Show Debugger" checkbox** + dead plumbing; and **HD-pack tile substitution** now applying in the debugger / tool render branch. Bugfix-only; with optional features off the shipped / native / `no_std` / wasm builds behave as v1.7.0 and AccuracyCoin holds 100% (139/139). |
656659
| **v1.7.0** | "Forge" — the tools become **writable** and **programmable**. **F** accuracy hardening (battery-save round-trip oracle + length-halt/reload + DMC even/odd-defer pins + an off-by-default PPU extra-scanlines overclock); mapper breadth → **168 families** (G1 reusable-ASIC: FK23C / COOLBOY / MINDKIDS / Sachen / Waixing / Kaiser, BestEffort honesty-gated); **A** editing-capable tools (palette / nametable / CHR / OAM writeback, an iNES / NES 2.0 header editor, an inline 6502 assembler); **C** debugger depth (CallstackManager + step modes, MemoryAccessCounter + uninit-read, ca65 / cc65 `.dbg` source maps); **B** scriptable **TAStudio** (`tastudio.*` Lua API + analysis-canvas callbacks) + full Lua parity; **E** host IPC / automation (`comm.*` / `client.*` / `userdata.*`) behind a new off-by-default `script-ipc` feature (host-mediated sandbox, ADR 0016); **D** rewind (HistoryViewer + Export-Last-30s `.rnm` + a Zwinder XOR-delta + LZ4 tiered greenzone); **G** expansion-audio (NSF router reusing VRC6/7 / FDS / MMC5 / N163 / 5B + MMC5 audio), movie import (`.fcm` / `.fmv` / `.vmv`; `.fm2` / `.bk2` export hashing), and an **HD-Pack Builder** (ADR 0017) + the HD-pack loader real-Mesen-`<tile>`-format fix (ADR 0018); a UI overhaul (consolidated Input Display, modernized menu / status bar, a polished Documentation pane); plus the **H1–H9 reach wave** — browser-RA finish + an RA HUD, spectator netplay + Game-Genie-encoder / `.srt` / `.tbl`, coverage-harness `.zip` / `.7z` / `.fds` discovery, per-game `<rom>.json` config overrides + a DIP editor + a lag counter (ADR 0019), audio depth (stereo panning / Schroeder reverb / crossfeed / output-device picker / 20-band EQ, ADR 0020), web/wasm parity (browser Lua / File-System-Access-API / Gamepad-API / PWA-offline / `?settings=` share-links, ADRs 0021+0022), an **i18n** framework (compile-time string catalog + a language picker, English + Spanish, ADR 0023), and a `full` maximal-native-feature build + `cargo full-run` alias. Additive / off-by-default; the shipped / native / `no_std` / wasm builds stay byte-identical and AccuracyCoin holds 100% (139/139). |
657660
| **v1.6.0** | "Studio" — creator-power + accuracy-polish + reach. A **TAStudio-class piano-roll TAS editor** (save-state greenzone + lag log + markers + forkable branches + `.rnmproj` projects, with a drag-paint egui grid + deterministic seek); **`.fm2` (FCEUX) and `.bk2` (BizHawk) movie interop** ↔ `.rnm`; **Lua movie driving** (`emu.run` / `emu.frameadvance`) + data breadth (memory domains, sized reads, `joypad`); **Mesen2-class debugger depth** (expression / conditional breakpoints, R/W/X watchpoints, a watch window, conditional trace, a full hex editor, RAM watch / search); an **off-axis accuracy** verification + documentation pass (DMC/OAM-DMA controller corruption, `$2007` read-during-render, sprite-overflow + open-bus decay — verified, no engine change); mapper breadth → **150 families** (J.Y. ASIC + a discrete-multicart batch) + a **UNIF (`.unf`) loader**; **FDS-proper** (timed disk-head + `$4032` auto-insert + per-game CRC quirk table); **A/V recording** (ffmpeg → mp4 / mkv, default-off `av-record`); **HD-pack HD audio** (`<bgm>` / `<sfx>` OGG via `$4100`, default-off `hd-pack`); and a **shader / filter ecosystem** (LMP88959 NTSC/PAL, hqNx / xBRZ, a constrained `.slangp` / `.cgp` import). Additive / off-by-default; the shipped / native / `no_std` / wasm builds stay byte-identical and AccuracyCoin holds 100% (139/139). |
@@ -787,7 +790,7 @@ If you use RustyNES in academic research, please cite:
787790
author = {RustyNES Contributors},
788791
title = {RustyNES: A Cycle-Accurate NES Emulator in Rust},
789792
year = {2026},
790-
version = {1.7.0},
793+
version = {1.8.1},
791794
url = {https://github.com/doublegate/RustyNES},
792795
note = {Cycle-accurate NES emulator on a master-clock-precise scheduler;
793796
AccuracyCoin 100\% (139/139), nestest 0-diff; 168 mapper families,

‎android/app/build.gradle.kts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ android {
3030
applicationId = "com.doublegate.rustynes"
3131
minSdk = 26 // AAudio floor.
3232
targetSdk = 35 // Play mandate since 2025-08-31.
33-
versionCode = 10800 // 1.8.0
34-
versionName = "1.8.0"
33+
versionCode = 10801 // 1.8.1
34+
versionName = "1.8.1"
3535
// No abiFilters here — set per buildType so release ships arm64 only
3636
// while debug keeps x86_64 for the emulator.
3737
}

‎android/app/src/main/java/com/doublegate/rustynes/Billing.kt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import com.android.billingclient.api.QueryPurchasesParams
2020
/** The one-time, non-consumable "Full Unlock" product id (set up in Play Console). */
2121
const val FULL_UNLOCK_PRODUCT = "full_unlock"
2222

23-
/** Free-tier demo session length: 10 minutes (shortened in debug for testing). */
24-
val DEMO_SESSION_SECONDS: Int = if (BuildConfig.DEBUG) 60 else 600
23+
/** Free-tier demo session length: 8 minutes (shortened in debug for testing). */
24+
val DEMO_SESSION_SECONDS: Int = if (BuildConfig.DEBUG) 60 else 480
2525

2626
/**
2727
* Owns the freemium entitlement (Workstream M).

‎docs/STATUS.md‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
# RustyNES — Project Status Matrix
22

3-
> **Current release: v1.7.1** (2026-06-19) — a bugfix / polish PATCH on **v1.7.0
4-
> "Forge"** (the feature release it patches; framed below). Additive / bugfix-only:
5-
> with the optional features off the shipped / native / `no_std` / wasm builds behave
6-
> as v1.7.0 (only the embedded `CARGO_PKG_VERSION` string + the seven fixes differ)
7-
> and **AccuracyCoin holds 100% (139/139)** (`cargo test --workspace --features
8-
> test-roms`: **1906 passed, 20 ignored**). The seven fixes: #1 ROM-close GPU abort
3+
> **Current release: v1.8.1 "Android"** (2026-06-19) — a patch on the first
4+
> **platform** (not accuracy) release **v1.8.0 "Android"**: a complete, shippable
5+
> Android app (shared `rustynes-mobile` UniFFI bridge + `rustynes-android` platform
6+
> crate + a Jetpack Compose shell; audio / touch+gamepad input /
7+
> save-states+SRAM+recent-ROMs / pause-FF-mute / foldable+immersive UI / AGSL
8+
> CRT+scanline filters / a freemium $2.99 Play unlock + an **8-minute** demo),
9+
> verified on a Galaxy Z Fold 7. v1.8.1 trims the demo from 10 → 8 minutes and
10+
> confirms the debug Full-Unlock override is **R8-stripped from the release AAB**.
11+
> The pure-Rust core is **byte-identical on ARM** (additive host only), so
12+
> **AccuracyCoin holds 100% (139/139)** and every desktop gate is unchanged. The
13+
> full wgpu-`SurfaceView` native renderer is the documented next increment. Built on
14+
> the bugfix PATCH **v1.7.1** on **v1.7.0 "Forge"** (framed below). v1.7.1's seven fixes: #1 ROM-close GPU abort
915
> (skip the `write_texture` upload on a pixel-slice length mismatch; `close_rom`
1016
> presents a clean blank frame) · #6 clean pause/unpause (pacing-timer
1117
> `break_phase()` reset on resume + a sticky audio pause gate → no `produced_max_ms`

0 commit comments

Comments
 (0)